package rmi;

import proof.*;
import Tools.*;
// import timingexp.Timeline;

import java.rmi.*;
import java.rmi.server.*;
import sun.rmi.server.*;
import sun.rmi.transport.*;

/**
 * This class holds a static worker method that stubs call to automatically
 * handle Snowflake authority challenges from servers.<p>
 *
 * Ideally, I would replumb RMI to do this in UnicastRef.invoke(), but
 * the current version of RMI makes such plumbing very difficult. I chose
 * this approach for expediency. The necessary changes to the stub
 * objects to use this helper method are trivial and mechanical, so it's
 * not an unreasonable shortcut.
 */
public class InvokeHack {
	/**
	 * This is the static worker method. It is designed to interpose
	 * on the lr.invoke() call made by RMI stub objects.
	 * It handles authority requests ({@link SfNeedAuthorizationException}s)
	 * by consulting a Prover object bound to the current thread.
	 */
    public static Object invoke(RemoteRef lr, Remote obj,
			 java.lang.reflect.Method method,
			 Object[] params,
			 long opnum)
	throws Exception
    {
//		Timeline.zeroTimer();
//		Timeline.timePoint("--- InvokeHack engage!");
		for (int tries=0; tries<2; tries++) {
			try {
//				Timeline.timePoint("sending RMI request");
				Object result = lr.invoke(obj, method, params, opnum);
//				Timeline.timePoint("got answer");
				return result;
			} catch (SfNeedAuthorizationException ex) {
//				Timeline.timePoint("caught ex");
				if (tries>0) {
					// System.out.println("InvokeHack already tried.");
					throw ex;
				}
				Prover2 prover = (Prover2) proverByThread.get();
				if (prover==null) {
					throw new RemoteException(
						"SSH Invoker: Thread has no Prover."
						+" Set with InvokeHack.setCurrentProver(...)");
				}
//				Timeline.timePoint("calling prover");
				Proof proof = prover.makeProof(ex.getIssuer(),
					ex.getSubject(), ex.getTag());
//				Timeline.timePoint("makeproof called");
				if (proof==null
					&& prover.isFinal(ex.getIssuer())) {
					proof = prover.createAuth(ex.getSubject(), ex.getIssuer());
//				Timeline.timePoint("createAuth called");
				}
				if (proof==null) {
					//System.out.println("I can't satisfy the requirements. "+
					//	"issuer is: "+prover.getName(ex.getIssuer()));
					throw ex;
				}
				// Send proof to destination mentioned in the exception
//				Timeline.timePoint("sending proof");
				ex.sendProof(proof);
//				Timeline.timePoint("proof transmitted");
				// loop around and try a second time.
				// System.out.println("InvokeHack.invoke looping");
			}
		}
		throw new RuntimeException("How did I get here?");
    }

	static PerThread proverByThread;
	static {
		proverByThread = new PerThread();
		// proverByThread.setDefault(new Prover("certs-client"));
	}

	/**
	 * Bind a Prover to the current thread.
	 */
	public static void setCurrentProver(Prover2 prover) {
		proverByThread.set(prover);
	}

	/**
	 * Retrieve the Prover bound to this thread that the {@link #invoke}
	 * method would use to authorize requests.
	 */
	public static Prover2 getCurrentProver() {
		return (Prover2) proverByThread.get();
	}
}
