/*
 *     Copyright (C) 2015  higherfrequencytrading.com
 *
 *     This program is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU Lesser General Public License as published by
 *     the Free Software Foundation, either version 3 of the License.
 *
 *     This program is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU Lesser General Public License for more details.
 *
 *     You should have received a copy of the GNU Lesser General Public License
 *     along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package net.openhft.lang.locks;

public final class VanillaReadWriteWithWaitsLockingStrategy extends AbstractReadWriteLockingStrategy
        implements ReadWriteWithWaitsLockingStrategy {

    private static final ReadWriteWithWaitsLockingStrategy INSTANCE =
            new VanillaReadWriteWithWaitsLockingStrategy();

    public static ReadWriteWithWaitsLockingStrategy instance() {
        return INSTANCE;
    }

    private VanillaReadWriteWithWaitsLockingStrategy() {}

    static final int RW_LOCK_LIMIT = 30;
    static final long RW_READ_LOCKED = 1L;
    static final long RW_WRITE_WAITING = 1L << RW_LOCK_LIMIT;
    static final long RW_WRITE_LOCKED = 1L << 2 * RW_LOCK_LIMIT;
    static final int RW_LOCK_MASK = (1 << RW_LOCK_LIMIT) - 1;

    static int rwReadLocked(long lock) {
        return (int) (lock & RW_LOCK_MASK);
    }

    static int rwWriteWaiting(long lock) {
        return (int) ((lock >>> RW_LOCK_LIMIT) & RW_LOCK_MASK);
    }

    static int rwWriteLocked(long lock) {
        return (int) (lock >>> (2 * RW_LOCK_LIMIT));
    }

    static <T> long read(NativeAtomicAccess<T> access, T t, long offset) {
        return access.getLongVolatile(t, offset);
    }

    static <T> boolean cas(NativeAtomicAccess<T> access, T t, long offset, long expected, long x) {
        return access.compareAndSwapLong(t, offset, expected, x);
    }

    @Override
    public <T> boolean tryReadLock(NativeAtomicAccess<T> access, T t, long offset) {
        long lock = read(access, t, offset);
        int writersWaiting = rwWriteWaiting(lock);
        int writersLocked = rwWriteLocked(lock);
        // readers wait for waiting writers
        if (writersLocked <= 0 && writersWaiting <= 0) {
            // increment readers locked.
            int readersLocked = rwReadLocked(lock);
            if (readersLocked >= RW_LOCK_MASK)
                throw new IllegalMonitorStateException("readersLocked has reached a limit of " +
                        readersLocked);
            if (cas(access, t, offset, lock, lock + RW_READ_LOCKED))
                return true;
        }
        return false;
    }

    @Override
    public <T> boolean tryWriteLock(NativeAtomicAccess<T> access, T t, long offset) {
        long lock = read(access, t, offset);
        int readersLocked = rwReadLocked(lock);
        int writersLocked = rwWriteLocked(lock);
        // writers don't wait for waiting readers.
        if (readersLocked <= 0 && writersLocked <= 0) {
            if (cas(access, t, offset, lock, lock + RW_WRITE_LOCKED))
                return true;
        }
        return false;
    }

    @Override
    public <T> boolean tryUpgradeReadToWriteLock(NativeAtomicAccess<T> access, T t, long offset) {
        throw new UnsupportedOperationException("not implemented yet");
    }

    @Override
    public <T> void readUnlock(NativeAtomicAccess<T> access, T t, long offset) {
        for (; ; ) {
            long lock = read(access, t, offset);
            int readersLocked = rwReadLocked(lock);
            if (readersLocked <= 0)
                throw new IllegalMonitorStateException("readerLock underflow");
            if (cas(access, t, offset, lock, lock - RW_READ_LOCKED))
                return;
        }
    }

    @Override
    public <T> void writeUnlock(NativeAtomicAccess<T> access, T t, long offset) {
        for (; ; ) {
            long lock = read(access, t, offset);
            int writersLocked = rwWriteLocked(lock);
            if (writersLocked != 1)
                throw new IllegalMonitorStateException("writersLock underflow " + writersLocked);
            if (cas(access, t, offset, lock, lock - RW_WRITE_LOCKED))
                return;
        }
    }

    @Override
    public <T> void downgradeWriteToReadLock(NativeAtomicAccess<T> access, T t, long offset) {
        throw new UnsupportedOperationException("not implemented yet");
    }

    @Override
    public boolean isWriteLocked(long state) {
        return rwWriteLocked(state) > 0;
    }

    @Override
    public int readLockCount(long state) {
        return rwReadLocked(state);
    }

    @Override
    public <T> void reset(NativeAtomicAccess<T> access, T t, long offset) {
        access.putOrderedLong(t, offset, 0L);
    }

    @Override
    public <T> void resetKeepingWaits(NativeAtomicAccess<T> access, T t, long offset) {
        while (true) {
            long lock = read(access, t, offset);
            long onlyWaits = lock & ((long) RW_LOCK_MASK) << RW_LOCK_LIMIT;
            if (cas(access, t, offset, lock, onlyWaits))
                return;
        }
    }

    @Override
    public <T> void registerWait(NativeAtomicAccess<T> access, T t, long offset) {
        for (; ; ) {
            long lock = read(access, t, offset);
            int writersWaiting = rwWriteWaiting(lock);
            if (writersWaiting >= RW_LOCK_MASK)
                throw new IllegalMonitorStateException("writersWaiting has reached a limit of " +
                        writersWaiting);
            if (cas(access, t, offset, lock, lock + RW_WRITE_WAITING))
                break;
        }
    }

    @Override
    public <T> void deregisterWait(NativeAtomicAccess<T> access, T t, long offset) {
        for (; ; ) {
            long lock = read(access, t, offset);
            int writersWaiting = rwWriteWaiting(lock);
            if (writersWaiting <= 0)
                throw new IllegalMonitorStateException("writersWaiting has underflowed");
            if (cas(access, t, offset, lock, lock - RW_WRITE_WAITING))
                break;
        }
    }

    @Override
    public <T> boolean tryWriteLockAndDeregisterWait(
            NativeAtomicAccess<T> access, T t, long offset) {
        long lock = read(access, t, offset);
        int readersLocked = rwReadLocked(lock);
        int writersWaiting = rwWriteWaiting(lock);
        int writersLocked = rwWriteLocked(lock);
        if (readersLocked <= 0 && writersLocked <= 0) {
            // increment readers locked.
            if (writersWaiting <= 0)
                throw new IllegalMonitorStateException("writersWaiting has underflowed");
            // add to the readLock count and decrease the readWaiting count.
            if (cas(access, t, offset, lock, lock + RW_WRITE_LOCKED - RW_WRITE_WAITING))
                return true;
        }
        return false;
    }

    @Override
    public <T> boolean tryUpgradeReadToWriteLockAndDeregisterWait(
            NativeAtomicAccess<T> access, T t, long offset) {
        throw new UnsupportedOperationException("not implemented yet");
    }

    @Override
    public long resetState() {
        return 0L;
    }

    @Override
    public <T> long getState(NativeAtomicAccess<T> access, T t, long offset) {
        return read(access, t, offset);
    }

    @Override
    public int waitCount(long state) {
        return rwWriteWaiting(state);
    }

    @Override
    public boolean isLocked(long state) {
        return isReadLocked(state) || isWriteLocked(state);
    }

    @Override
    public int lockCount(long state) {
        return rwReadLocked(state) + rwWriteLocked(state);
    }

    @Override
    public String toString(long state) {
        return "[read locks = " + readLockCount(state) +
                ", write locked = " + isWriteLocked(state) +
                ", waits = " + waitCount(state) + "]";
    }

    @Override
    public int sizeInBytes() {
        return 8;
    }
}
