/*
 * Written by Dawid Kurzyniec and released to the public domain, as explained
 * at http://creativecommons.org/licenses/publicdomain
 */

package edu.emory.mathcs.util.remote.locks;

import java.rmi.*;
import edu.emory.mathcs.backport.java.util.concurrent.*;
import edu.emory.mathcs.backport.java.util.concurrent.helpers.*;


/**
 * @author Dawid Kurzyniec
 */
public abstract class AbstractPollingLock implements RemoteLock {

    final int retries;
    final float backoff;
    final long initDelay;
    final long maxDelay;

    protected AbstractPollingLock() {
        this(1.6f, TimeUnit.MILLISECONDS.toNanos(100),
                   TimeUnit.MINUTES.toNanos(1));
    }

    protected AbstractPollingLock(float backoff, long initDelay, long maxDelay) {
        this(3, backoff, initDelay, maxDelay);
    }

    protected AbstractPollingLock(int retries, float backoff, long initDelay, long maxDelay) {
        this.retries = retries;
        this.backoff = backoff;
        this.initDelay = initDelay;
        this.maxDelay = maxDelay;
    }

    /**
    * {@inheritDoc}
    */
    public void lock() throws RemoteException {
        boolean wasInterrupted = false;
        while (true) {
            try {
                lockInterruptibly();
                if (wasInterrupted) {
                    Thread.currentThread().interrupt();
                }
                return;
            }
            catch (InterruptedException e) {
                wasInterrupted = true;
            }
        }
    }

    /**
    * {@inheritDoc}
    */
    public void lockInterruptibly() throws InterruptedException, RemoteException {
        if (tryLock()) return;
        for (int i=0; i<retries; i++) {
            if (tryLock()) return;
        }
        long delay = initDelay;
        while (true) {
            TimeUnit.NANOSECONDS.sleep(delay);
            if (tryLock()) return;
            delay = calculateNextDelay(delay);
        }
    }

    /**
     * {@inheritDoc}
     */
    public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException,
                                                               RemoteException {
        long nanos = unit.toNanos(timeout);
        long deadline = Utils.nanoTime() + nanos;

        if (nanos <= 0) return false;
        if (tryLock()) return true;
        nanos = deadline - Utils.nanoTime();

        for (int i=0; i<retries; i++) {
            if (nanos <= 0) return false;
            if (tryLock()) return true;
            nanos = deadline - Utils.nanoTime();
        }

        long delay = initDelay;
        while (true) {
            if (nanos <= 0) return false;
            TimeUnit.NANOSECONDS.sleep(delay < nanos ? delay : nanos);
            if (tryLock()) return true;
            nanos = deadline - Utils.nanoTime();
            delay = calculateNextDelay(delay);
        }

    }

    public RemoteCondition newCondition() {
        // Not implemented
        throw new UnsupportedOperationException();
    }

    private long calculateNextDelay(long prev) {
        long delay = (long)(prev * backoff);
        if (delay > maxDelay) delay = maxDelay;
        return delay;
    }
}
