/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.river.test.impl.norm;

import java.io.PrintWriter;

import net.jini.core.lease.Lease;
import net.jini.core.lease.LeaseDeniedException;
import net.jini.core.lease.UnknownLeaseException;

import java.util.logging.Level;


/**
 * Simple owner that lets the renews the lease when asked. Ensures that
 * the lease has not expired and that each time it is renewed it has
 * be asked for the correct duration
 */
public class RenewAtOwner extends LeaseOwner {
    /** String to be returned by <code>didPass</code> */
    private String rslt = null;

    /** Max time we are will to grant on a renewal request */
    final private long maxExtension;

    /** Desired expiration renewal service should be renewing towards */
    final private long desiredExpiration;

    /** The max value we expect for each renewal request */
    final private long desiredRenewal;

    /** Max acceptable varaion from desiredExpiration for a renewal request */
    final private long slop;

    /** Current expiration time of lease */
    private long expiration;

    /** Time of last renewal */
    private long lastRenewal;

    /** Object to notify if we fail */
    private Object notifyOnFailure;

    /** 
     * Simple constructor 
     * @param initialExpiration Initial expiration time for lease.
     * @param maxExtension Maximum time this owner will be willing to extend 
     *                     the lease
     * @param desiredExpiration Every lease a renewal service is renewing
     *              has a "membership expiration" associated with it.  The
     *              renewal service should all-ways pick a renewal duration
     *              that when added to the current time equals this membership
     *              expiration time.
     * @param slop Allowable variance from desired expiration when making a
     *             renewal request.
     * @param desiredRenewal
     *             Expect value of the renewDuration parameter
     * @param notifyOnFailure
     *             Object to notify if there is a failure
     */
    public RenewAtOwner(long initialExpiration, long maxExtension,
			long desiredExpiration, long slop,
			long desiredRenewal, Object notifyOnFailure) 
    {
	this.desiredRenewal = desiredRenewal;
	this.maxExtension = maxExtension;
	this.desiredExpiration = desiredExpiration;
	this.slop = slop;
	expiration = initialExpiration;
	this.notifyOnFailure = notifyOnFailure;
    }

    /**
     * Retrun true if the lease has not expired
     */
    private boolean ensureCurrent(long now) {
	return now < expiration + slop;       
    }

    /**
     * Return null if we dected no error, and a disciptive string otherwise
     */
    String didPass() {
	if (rslt != null) 
	    return rslt;

	// Make sure the lease has not expired (unless it should have)
	synchronized (this) {
	    final long now = System.currentTimeMillis();
	    final long diff = Math.abs(desiredExpiration - expiration);

	    if (!ensureCurrent(now) && diff > slop)
		rslt = "Lease expired";
	}

	return rslt;	    
    }
    
    /**
     * Set rslt string if it is not already set and set renwals to zero
     */
    private void setRsltIfNeeded(String newResult) {
	if (rslt == null) {	    
	    rslt = newResult;
	}
    }

    private synchronized long batchRenewSync(long extension) 
	throws UnknownLeaseException, LeaseDeniedException 
    {
	final long now = System.currentTimeMillis();
	lastRenewal = now;

	logger.log(Level.INFO, "batchRenew(" + desiredExpiration + "," + 
		    desiredRenewal + ")" + ":now=" + now + " extension=" +
		    extension);

	// Has the lease expired?
	if (expiration < now) {
	    if (!ensureCurrent(now))
		setRsltIfNeeded("Renwal service let lease expire");
	    throw new UnknownLeaseException("Lease expired");
	}

	// Have the asked for the right renewal?
	
	// Are they supposted to ask for Lease.ANY, and if they
	// are did they?
	if (desiredExpiration == Lease.FOREVER && desiredRenewal == Lease.ANY) {
	    if (extension != Lease.ANY) {
		setRsltIfNeeded("LRS asked for " + extension +
				" when it should have asked for Lease.ANY");
		throw new LeaseDeniedException("Next time ask for Lease.ANY");
	    }
	} else if (extension > desiredRenewal) {
	    // If desiredRenewal is not Lease.ANY extension must be less
	    // than or equal to desiredRenewal
	    setRsltIfNeeded("LRS asked for " + extension + " when it " +
			    "should have asked for no more than " +
			    desiredRenewal);
	    throw new LeaseDeniedException("Next time ask for no more than " +
					   desiredRenewal);
	} else if (desiredRenewal + slop > desiredExpiration - now) {
	    // Make sure they are asking for the remander of the
	    // desiredExpiration
	    
	    final long requestedExp = now + extension;
	    if (requestedExp < 0) {
		// Overflow, ignore slop and just check to see if
		// desired expiration is forever
		if (desiredExpiration != Long.MAX_VALUE) {
		    setRsltIfNeeded("Renewal service asked for a new " +
		        "expiration of " + requestedExp + 
			" should have asked for " + desiredExpiration +
			" (a diffrence of " + 
			(requestedExp - desiredExpiration) + " milliseconds)");
		    throw new LeaseDeniedException("Too MUCH!");
		}
	    } else {
		// No overflow...no...forever check to see if the diff
		// is less than the slop, or if it asked for
		// desiredRenewal
		final long diff = Math.abs(requestedExp - desiredExpiration);
		if (diff > slop) {
		    setRsltIfNeeded("Renewal service asked for a new " +
			"expiration of " + requestedExp + 
			" should have asked for " + desiredExpiration +
			" (a diffrence of " + diff + " milliseconds)");
		    throw new LeaseDeniedException("A bit off");
		}
	    }
	} else if (desiredRenewal != extension) {
	    // Should have asked for exactly desiredRenewal
	    setRsltIfNeeded("Renewal service asked for an extention " +
		"of " + extension + " should have asked for " + desiredRenewal);
	    
	    throw new LeaseDeniedException("Next time ask for " +
					   desiredRenewal);	    
	}
		
	// Seem to ask for the right extension
	// Calculate the renewal grant __DURATION__
	
	long grant;
	if (extension == Lease.ANY) {
	    grant = maxExtension;
	} else {
	    grant = Math.min(maxExtension, extension);
	}
	
	expiration = now + grant;
	if (expiration < 0) {
	    // Overflow
	    expiration = Long.MAX_VALUE;
	    grant = Long.MAX_VALUE - now;
	}
	
	logger.log(Level.INFO, "batchRenew(" + desiredExpiration + "," + desiredRenewal
		    + ") granted:" + grant +" new exp:" + expiration);
	return grant;
    }

    // Inherit java doc from super type
    public long batchRenew(long extension) 
	     throws UnknownLeaseException, LeaseDeniedException 
    {
	try {
	    return batchRenewSync(extension);
	} finally {
	    // If after all this rslt is non null we have to notify 
	    // notifyOnFailure
	    if (rslt != null) {
		synchronized (notifyOnFailure) {
		    notifyOnFailure.notifyAll();
		} 
	    }
	}
    }

    /**
     * Return the lease desired duration we are shotting for
     */
    long getDesiredDuration() {
	if (desiredExpiration  == Lease.FOREVER) 
	    return Lease.FOREVER;
	else
	    return desiredExpiration - System.currentTimeMillis();
    }

    /**
     * Return the desired renewal request
     */
    long getDesiredRenewal() {
	return desiredRenewal;
    }

    // Inherit java doc from super type
    public Object renew(long extension) 
	throws UnknownLeaseException, LeaseDeniedException
    {
	return new Long(batchRenew(extension));
    }

    // Inherit java doc from super type
    public void batchCancel() {
	setRsltIfNeeded("Renewal Service canceld lease!");
    }

    // Inherit java doc from super type
    public Throwable cancel() {
	batchCancel();
	return null;
    }

    /** 
     * Returns the expiration time of the lease
     */
    public synchronized long getExpiration() {
	return expiration;
    }

    /** 
     * Returns the time of the last renewal call
     */
    public synchronized long getLastRenewTime() {
	return lastRenewal;
    }    
}
