/*
 * 
 * Copyright 2004 Sun Microsystems, Inc. All Rights Reserved.
 * 
 * The contents of this file are subject to the Sun Community Source
 * License v3.0/Jini Technology Specific Attachment v 1.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://wwws.sun.com/software/jini/licensing/SCSL3_JiniTSA1.html.
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
 * the License for the specific language governing rights and limitations
 * under the License.
 * 
 * The Reference Code is Jini Technology Core Platform code, v2.0. The
 * Developer of the Reference Code is Sun Microsystems, Inc.
 * 
 * Contributor(s): Sun Microsystems, Inc.
 * 
 * The contents of this file comply with the Jini Technology Core
 * Platform Compatibility Kit, v2.0A.
 * 
 * Tester(s): Sun Microsystems, Inc.
 * 
 * Test Platform(s):
 * Java 2 SDK, Standard Edition, Version 1.4.2_03 for Solaris(TM)SPARC/x86
 * Java 2 SDK, Standard Edition, Version 1.4.2_03 for Linux (Intel x86)
 * Java 2 SDK, Standard Edition, Version 1.4.2_03 for Windows (Intel Platform)
 * 
 */
package com.sun.jini.outrigger;

import java.io.IOException;
import java.rmi.RemoteException;
import java.rmi.MarshalledObject;
import net.jini.core.event.RemoteEventListener;
import net.jini.core.event.RemoteEvent;
import net.jini.core.event.UnknownEventException;
import net.jini.id.Uuid;
import net.jini.security.ProxyPreparer;
import com.sun.jini.landlord.LeasedResource;

/**
 * Subclass of <code>TransitionWatcher</code> for event
 * registrations. Also represents the registration itself.
 */
abstract class EventRegistrationWatcher extends TransitionWatcher 
    implements LeasedResource
{
    /** 
     * The current expiration time of the registration
     * Protected, but only for use by subclasses.
     */
    long expiration;

    /** 
     * The UUID that identifies this registration 
     * Protected, but only for use by subclasses.
     * Should not be changed.
     */
    Uuid cookie;

    /**
     * The handback associated with this registration.
     * Protected, but only for use by subclasses.
     * Should not be changed.
     */
    MarshalledObject handback;

    /** 
     * The event ID associated with this registration
     * Protected, but only for use by subclasses.
     * Should not be changed.
     */
    long eventID;

    /** 
     * The current sequence number. 
     */
    private long currentSeqNum = 0;

    /**
     * The sequence number of the last event successfully 
     * delivered.  Protected, but only for use by subclasses.
     */
    long lastSeqNumDelivered = -1;

    /**
     * Used during log recovery to create a mostly empty
     * <code>EventRegistrationWatcher</code>.  
     * <p> 
     * Note, we set the time stamp and tie-breaker here instead of
     * getting them from the log. This means they will be inconstant
     * with their value from the last VM we were in, but since they
     * never leak out and events are read-only anyway this should not
     * be a problem (this also allows us to keep the tie-breaker and
     * time stamp in final fields).
     *
     * @param timestamp the value that is used
     *        to sort <code>TransitionWatcher</code>s.
     * @param startOrdinal the highest ordinal associated
     *        with operations that are considered to have occurred 
     *        before the operation associated with this watcher.
     * @param currentSeqNum Sequence number to start with.
     */
    EventRegistrationWatcher(long timestamp, long startOrdinal, 
			     long currentSeqNum) 
    {
	super(timestamp, startOrdinal);
	this.currentSeqNum = currentSeqNum;
    }

    /**
     * Create a new <code>EventRegistrationWatcher</code>.
     * @param timestamp the value that is used
     *        to sort <code>TransitionWatcher</code>s.
     * @param startOrdinal the highest ordinal associated
     *        with operations that are considered to have occurred 
     *        before the operation associated with this watcher.
     * @param cookie The unique identifier associated
     *        with this watcher. Must not be <code>null</code>.
     * @param handback The handback object that
     *        should be sent along with event
     *        notifications to the the listener.
     * @param eventID The event ID for event type
     *        represented by this object.
     * @throws NullPointerException if the <code>cookie</code>
     *        argument is <code>null</code>.
     */
    EventRegistrationWatcher(long timestamp, long startOrdinal, Uuid cookie,
	MarshalledObject handback, long eventID)
    {
	super(timestamp, startOrdinal);

	if (cookie == null)
	    throw new NullPointerException("cookie must be non-null");

	this.cookie = cookie;
	this.handback = handback;
	this.eventID = eventID;
    }

    /**
     * Process the given transition by queuing up a task with the
     * notifier for event delivery. Assumes the passed entry matches
     * the template in the <code>TemplateHandle</code> associated with
     * this watcher and that <code>isInterested</code> returned
     * <code>true</code>. If <code>remove</code> has been called or
     * the expiration time of this watcher has passed, this call
     * should have no effect. This call may cause the watcher to be
     * removed.
     * @param transition A <code>EntryTransition</code> that
     *              describes the transition and what
     *              entry is transitioning. This method
     *              will assume that <code>transition.getHandle</code>
     *              returns a non-null value.
     * @param now   An estimate of the current time (not the time
     *              when the event occured).
     * @throws NullPointerException if <code>transition</code> is 
     *         <code>null</code>.  
     */
    void process(EntryTransition transition, long now) {
	boolean doneFor = false;

	// lock before checking the time and so we can update currentSeqNum
	synchronized (this) {
	    if (owner == null) 
		return; // Must have been removed

	    if (now > expiration) {
		doneFor = true;
	    } else {
		currentSeqNum++;
		owner.getServer().enqueueDelivery(new BasicEventSender());
	    }
	}

	if (doneFor)
	    cancel();
    }


    /**
     * Return the remote listener associated with this 
     * <code>EventRegistrationWatcher</code>. Optionally
     * prepare the listener if it has been recovered from
     * the store and not yet re-prepared.
     * @return the remote listener associated with this 
     * <code>EventRegistrationWatcher</code>. 
     * @throws IOException if the listener can not
     *         be unmarshalled. May throw {@link RemoteException}
     *         if the call to the preparer does
     * @throws ClassNotFoundException if the listener
     *         needs to be unmarshalled and a necessary
     *         class can not be found.
     * @throws SecurityException if the <code>prepareProxy</code>
     *         call does.
     */
    abstract RemoteEventListener getListener(ProxyPreparer preparer)
	throws ClassNotFoundException, IOException;

    /**
     * Set the expiration time of this object.  This method may be
     * called before <code>setTemplateHandle</code> is called.
     * Assumes locking is handled by the caller.
     * @param newExpiration The expiration time.
     */
    public void setExpiration(long newExpiration) {
	expiration = newExpiration;
    }

    public long getExpiration() {
	return expiration;
    }

    /**
     * Get the unique identifier associated with this object.  This
     * method may be called before <code>setTemplateHandle</code> is
     * called. 
     * @return The unique identifier associated with this
     * watcher.
     */
    public Uuid getCookie() {
	return cookie;
    }

    /**
     * Overridden by subclasses if there is any cleanup work they need
     * to do as part of <code>cancel</code> or
     * <code>removeIfExpired</code>. Called after releasing the lock
     * on <code>this</code>.  Will be called at most once.  
     * @param owner A reference to the owner.
     * @param expired <code>true</code> if being called from 
     *        <code>removeIfExpired</code> and false otherwise. 
     */
    void cleanup(TemplateHandle owner, boolean expired) 
    {}

    /**
     * The heavy lifting of removing ourselves.
     * @param now The current time (or a bit earlier).
     * @param doIt If <code>true</code> ignore
     *             <code>now</code> and just remove <code>this</code>
     *             object.
     * @return <code>true</code> if this call removed
     *         <code>this</code> object, <code>false</code> if
     *         it had already been done. Should be ignored if 
     *         <code>doIt</code> is <code>false</code>.
     */
    private boolean doRemove(long now, boolean doIt) {
	final TemplateHandle owner;
	synchronized (this) {
	    if (this.owner == null)
		return false; // already removed

	    // Is this a force, or past our expiration?
	    if (!doIt && (now < expiration))
		return false; // don't remove, not our time

	    owner = this.owner;
	    expiration = Long.MIN_VALUE; //Make sure no one tries to renew us
	    this.owner = null;       //We might stick around don't hold owner
	}

	cleanup(owner, !doIt);
	owner.getServer().removeEventRegistration(this);	    
	owner.removeTransitionWatcher(this);	
	return true; // we did the deed
    }

    void removeIfExpired(long now) {
	doRemove(now, false);
    }

    /**
     * Make sure this object will be removed from the system
     * independent of the expiration time.
     * @return <code>true</code> if this call was
     * the first removal attempt.
     */
    boolean cancel() {
	return doRemove(0, true);
    }

    /**
     * Common implementation of <code>EventSender</code>.
     * Protected but for use only by sub-classes.
     */
    private class BasicEventSender implements EventSender {
	public void sendEvent(Object source, long now, 
			      ProxyPreparer preparer)
	    throws UnknownEventException, IOException, ClassNotFoundException
	{
	    boolean doneFor = false;
	    long seqNum = -1;

	    synchronized (EventRegistrationWatcher.this) {
		if (owner == null)
		    return; // Our watcher must have been removed, we're done

		if (getExpiration() < now) {
		    doneFor = true; // Our watcher is expired, remove it
		} else if (currentSeqNum <= lastSeqNumDelivered) {
		    return; // Someone already sent our event, we're done
		} else {
		    // We need to send an event!
		    seqNum = currentSeqNum;
		}
	    }

	    // Now that we are outside the lock kill our watcher if doneFor.
	    if (doneFor) {
		cancel();
		return;
	    }

	    /* The only way to get here is through a path that sets
	     * seqNum to the non-initial values
	     */
	    assert seqNum != -1;

	    /* If we are here then we need to send an event (probably
	     * someone could have sent our event between the time
	     * we released the lock and now).
	     */
	    getListener(preparer).notify(
		new RemoteEvent(source, eventID, seqNum, handback));

	    // success!, update lastSeqNumDelivered, but don't go backward
	    synchronized (EventRegistrationWatcher.this) {
		if (seqNum > lastSeqNumDelivered)
		    lastSeqNumDelivered = seqNum;
	    }
	}
	
	
	public void cancelRegistration() {
	    cancel();
	}

	/**
	 * Return the <code>EventRegistrationWatcher</code> this
	 * object is part of (exits because
	 * <code>(BasicEventSender)other).EventRegistrationWatcher.
	 * this</code> does not work.
	 */
	private EventRegistrationWatcher getOwner() {
	    return EventRegistrationWatcher.this;
	}

	/**
	 * Run after another event sender if it is for the same
	 * registration. No sense sending the same event twice. Don't
	 * care which one goes first (though the second probably won't
	 * run since <code>lastSeqNumDelivered</code> will probably
	 * equal <code>currentSeqNum</code> when it runs).  
	 */
	public boolean runAfter(EventSender other) {
	    if (!(other instanceof BasicEventSender))
		return false;

	    return EventRegistrationWatcher.this ==
		((BasicEventSender)other).getOwner();
	}
    }
}
