package com.njzscloud.common.core.thread;

import com.njzscloud.common.core.ex.Exceptions;
import com.njzscloud.common.core.ex.SysThrowable;

import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class SyncUtil {

    public static void sync(String blockName, long timeout, boolean fair, SyncBlock syncBlock) {
        LockWrapper lockWrapper = null;
        try {
            lockWrapper = LockWrapper.get(blockName, timeout, fair);
            if (lockWrapper != null) {
                // 加锁成功
                syncBlock.block();
            } else {
                throw Exceptions.error("锁获取失败：锁名称：{}", blockName);
            }
        } catch (SysThrowable e) {
            throw e;
        } catch (Exception e) {
            throw Exceptions.error(e, "同步块执行失败：锁名称：{}", blockName);
        } finally {
            if (lockWrapper != null) {
                lockWrapper.unlock();
            }
        }
    }

    public static <T> T sync(String blockName, long timeout, boolean fair, SyncSupplierBlock<T> syncBlock) {
        LockWrapper lockWrapper = null;
        try {
            lockWrapper = LockWrapper.get(blockName, timeout, fair);
            if (lockWrapper != null) {
                // 加锁成功
                return syncBlock.block();
            } else {
                throw Exceptions.error("锁获取失败：锁名称：{}", blockName);
            }
        } catch (SysThrowable e) {
            throw e;
        } catch (Exception e) {
            throw Exceptions.error(e, "同步块执行失败：锁名称：{}", blockName);
        } finally {
            if (lockWrapper != null) {
                lockWrapper.unlock();
            }
        }
    }

    public static <T> T sync(String blockName, boolean fair, SyncSupplierBlock<T> syncBlock) {
        return sync(blockName, 0, fair, syncBlock);
    }

    public static <T> T sync(String blockName, long timeout, SyncSupplierBlock<T> syncBlock) {
        return sync(blockName, timeout, false, syncBlock);
    }

    public static <T> T sync(String blockName, SyncSupplierBlock<T> syncBlock) {
        return sync(blockName, 0, false, syncBlock);
    }

    public static void sync(String blockName, boolean fair, SyncBlock syncBlock) {
        sync(blockName, 0, fair, syncBlock);
    }

    public static void sync(String blockName, long timeout, SyncBlock syncBlock) {
        sync(blockName, timeout, false, syncBlock);
    }

    public static void sync(String blockName, SyncBlock syncBlock) {
        sync(blockName, 0, false, syncBlock);
    }

    public static void syncR(String blockName, long timeout, boolean fair, SyncBlock syncBlock) {
        LockWrapper lockWrapper = null;
        try {
            lockWrapper = LockWrapper.getRLock(blockName, timeout, fair);
            if (lockWrapper != null) {
                // 加锁成功
                syncBlock.block();
            } else {
                throw Exceptions.error("锁获取失败：锁名称：{}", blockName);
            }
        } catch (SysThrowable e) {
            throw e;
        } catch (Exception e) {
            throw Exceptions.error(e, "同步块执行失败：锁名称：{}", blockName);
        } finally {
            if (lockWrapper != null) {
                lockWrapper.unrlock();
            }
        }
    }

    public static <T> T syncR(String blockName, long timeout, boolean fair, SyncSupplierBlock<T> syncBlock) {
        LockWrapper lockWrapper = null;
        try {
            lockWrapper = LockWrapper.getRLock(blockName, timeout, fair);
            if (lockWrapper != null) {
                // 加锁成功
                return syncBlock.block();
            } else {
                throw Exceptions.error("锁获取失败：锁名称：{}", blockName);
            }
        } catch (SysThrowable e) {
            throw e;
        } catch (Exception e) {
            throw Exceptions.error(e, "同步块执行失败：锁名称：{}", blockName);
        } finally {
            if (lockWrapper != null) {
                lockWrapper.unrlock();
            }
        }
    }

    public static <T> T syncR(String blockName, boolean fair, SyncSupplierBlock<T> syncBlock) {
        return sync(blockName, 0, fair, syncBlock);
    }

    public static <T> T syncR(String blockName, long timeout, SyncSupplierBlock<T> syncBlock) {
        return sync(blockName, timeout, false, syncBlock);
    }

    public static <T> T syncR(String blockName, SyncSupplierBlock<T> syncBlock) {
        return sync(blockName, 0, false, syncBlock);
    }

    public static void syncR(String blockName, boolean fair, SyncBlock syncBlock) {
        sync(blockName, 0, fair, syncBlock);
    }

    public static void syncR(String blockName, long timeout, SyncBlock syncBlock) {
        sync(blockName, timeout, false, syncBlock);
    }

    public static void syncR(String blockName, SyncBlock syncBlock) {
        sync(blockName, 0, false, syncBlock);
    }

    public static void syncW(String blockName, long timeout, boolean fair, SyncBlock syncBlock) {
        LockWrapper lockWrapper = null;
        try {
            lockWrapper = LockWrapper.getWLock(blockName, timeout, fair);
            if (lockWrapper != null) {
                // 加锁成功
                syncBlock.block();
            } else {
                throw Exceptions.error("锁获取失败：锁名称：{}", blockName);
            }
        } catch (SysThrowable e) {
            throw e;
        } catch (Exception e) {
            throw Exceptions.error(e, "同步块执行失败：锁名称：{}", blockName);
        } finally {
            if (lockWrapper != null) {
                lockWrapper.unwlock();
            }
        }
    }

    public static <T> T syncW(String blockName, long timeout, boolean fair, SyncSupplierBlock<T> syncBlock) {
        LockWrapper lockWrapper = null;
        try {
            lockWrapper = LockWrapper.getWLock(blockName, timeout, fair);
            if (lockWrapper != null) {
                // 加锁成功
                return syncBlock.block();
            } else {
                throw Exceptions.error("锁获取失败：锁名称：{}", blockName);
            }
        } catch (SysThrowable e) {
            throw e;
        } catch (Exception e) {
            throw Exceptions.error(e, "同步块执行失败：锁名称：{}", blockName);
        } finally {
            if (lockWrapper != null) {
                lockWrapper.unwlock();
            }
        }
    }

    public static <T> T syncW(String blockName, boolean fair, SyncSupplierBlock<T> syncBlock) {
        return sync(blockName, 0, fair, syncBlock);
    }

    public static <T> T syncW(String blockName, long timeout, SyncSupplierBlock<T> syncBlock) {
        return sync(blockName, timeout, false, syncBlock);
    }

    public static <T> T syncW(String blockName, SyncSupplierBlock<T> syncBlock) {
        return sync(blockName, 0, false, syncBlock);
    }

    public static void syncW(String blockName, boolean fair, SyncBlock syncBlock) {
        sync(blockName, 0, fair, syncBlock);
    }

    public static void syncW(String blockName, long timeout, SyncBlock syncBlock) {
        sync(blockName, timeout, false, syncBlock);
    }

    public static void syncW(String blockName, SyncBlock syncBlock) {
        sync(blockName, 0, false, syncBlock);
    }

    public interface SyncBlock {
        void block();
    }

    public interface SyncSupplierBlock<T> {
        T block();
    }

    private static class LockWrapper {
        private static final Map<String, LockWrapper> LOCKS = new ConcurrentHashMap<>();
        private final String blockName;
        private final ReentrantLock lock;
        private final ReentrantReadWriteLock rwlock;
        private int count;

        private LockWrapper(String blockName, boolean fair) {
            this(blockName, fair, false);
        }

        private LockWrapper(String blockName, boolean fair, boolean rw) {
            if (rw) {
                rwlock = new ReentrantReadWriteLock(fair);
                lock = null;
            } else {
                lock = new ReentrantLock(fair);
                rwlock = null;
            }
            this.count = 0;
            this.blockName = blockName;
        }

        public static LockWrapper getRLock(String blockName, long timeout, boolean fair) {
            LockWrapper lockWrapper = LOCKS.compute(blockName, (k, v) -> {
                LockWrapper wrapper = Objects.requireNonNullElseGet(v, () -> new LockWrapper(blockName, fair, true));
                // 增加引用数
                wrapper.count++;
                return wrapper;
            });
            if (timeout > 0) {
                if (!lockWrapper.tryRLock(timeout)) {
                    // 加锁失败，回退引用数
                    LOCKS.compute(blockName, (k, v) -> {
                        if (v == null) return null;
                        v.count--;
                        return v.count > 0 ? v : null;
                    });
                    return null;
                } else {
                    return lockWrapper;
                }
            } else {
                lockWrapper.rlock();
                return lockWrapper;
            }
        }

        public static LockWrapper getWLock(String blockName, long timeout, boolean fair) {
            LockWrapper lockWrapper = LOCKS.compute(blockName, (k, v) -> {
                LockWrapper wrapper = Objects.requireNonNullElseGet(v, () -> new LockWrapper(blockName, fair, true));
                // 增加引用数
                wrapper.count++;
                return wrapper;
            });
            if (timeout > 0) {
                if (!lockWrapper.tryWLock(timeout)) {
                    // 加锁失败，回退引用数
                    LOCKS.compute(blockName, (k, v) -> {
                        if (v == null) return null;
                        v.count--;
                        return v.count > 0 ? v : null;
                    });
                    return null;
                } else {
                    return lockWrapper;
                }
            } else {
                lockWrapper.wlock();
                return lockWrapper;
            }
        }

        public static LockWrapper get(String blockName, long timeout, boolean fair) {
            LockWrapper lockWrapper = LOCKS.compute(blockName, (k, v) -> {
                LockWrapper wrapper = Objects.requireNonNullElseGet(v, () -> new LockWrapper(blockName, fair));
                // 增加引用数
                wrapper.count++;
                return wrapper;
            });
            if (timeout > 0) {
                if (!lockWrapper.tryLock(timeout)) {
                    // 加锁失败，回退引用数
                    LOCKS.compute(blockName, (k, v) -> {
                        if (v == null) return null;
                        v.count--;
                        return v.count > 0 ? v : null;
                    });
                    return null;
                } else {
                    return lockWrapper;
                }
            } else {
                lockWrapper.lock();
                return lockWrapper;
            }
        }

        private void rlock() {
            rwlock.readLock().lock();
        }

        private boolean tryRLock(long timeout) {
            try {
                return rwlock.readLock().tryLock(timeout, TimeUnit.MICROSECONDS);
            } catch (InterruptedException e) {
                return false;
            }
        }

        private void wlock() {
            rwlock.writeLock().lock();
        }

        private boolean tryWLock(long timeout) {
            try {
                return rwlock.writeLock().tryLock(timeout, TimeUnit.MICROSECONDS);
            } catch (InterruptedException e) {
                return false;
            }
        }

        private void lock() {
            lock.lock();
        }

        private boolean tryLock(long timeout) {
            try {
                return lock.tryLock(timeout, TimeUnit.MICROSECONDS);
            } catch (InterruptedException e) {
                return false;
            }
        }

        public void unrlock() {
            // 正常解锁
            rwlock.readLock().unlock();
            LOCKS.compute(blockName, (k, v) -> {
                if (v == null) return null;
                v.count--;
                return v.count > 0 ? v : null;
            });
        }

        public void unwlock() {
            // 正常解锁
            rwlock.writeLock().unlock();
            LOCKS.compute(blockName, (k, v) -> {
                if (v == null) return null;
                v.count--;
                return v.count > 0 ? v : null;
            });
        }

        private void unlock() {
            lock.unlock();
            LOCKS.compute(blockName, (k, v) -> {
                if (v == null) return null;
                v.count--;
                return v.count > 0 ? v : null;
            });
        }

    }
}
