package proof;

import java.util.*;

import sdsi.*;
import sdsi.sexp.*;
import Tools.*;

/**
 * The abstract superclass of the ``self-verifying'' proofs. When a
 * server receives a proof from a client, it arrives as a SPKI ({@link
 * sdsi}) S-expression that gets parsed into a Proof class. The class
 * file is loaded locally (so that the client cannot fool the server by
 * sending a proof with a <code>verify() { return true; }</code>
 * method).<p>
 * 
 * The server can ask a proof if its conclusion is valid, or if the
 * proof is valid <em>and</em> it supports a proposed statement.
 */
public abstract class Proof
	extends SDSIObject {

	transient boolean verifiedOnce = false;

	/**
	 * the subject is the principal who stands to gain from this proof,
	 * for the proof shows that he speaks for the issuer (possibly with
	 * restrictions).
	 */
	public abstract Subject getSubject();

	/**
	 * who this proof ultimately speaks for
	 */
	public abstract SDSIPrincipal getIssuer();

	/**
	 * the tag represents the set of requests this proof is valid for
	 * (SPKIwise, this is the output of AIntersect.)
	 */
	public abstract Tag getTag();

	/**
	 * Add the preorder traversal of this proof node and its children to
	 * the end of 'addToMe'.
	 */
	abstract void preorder(List addToMe, int what);

	static final int ISSUERS = 17;
	static final int CERTIFICATES = 18;
	static final int PROOFS = 19;

	/**
	 * Return a list of the issuers involved in this proof in
	 * preorder-traversal order. Used in proof digestion.
	 */
	public List preorderIssuers() { return preorder(ISSUERS); }

	/**
	 * Return a list of the certificates involved in this proof in
	 * preorder-traversal order. Used in proof digestion.
	 */
	public List preorderCertificates() { return preorder(CERTIFICATES); }

	/**
	 * Return a list of the subproofs (lemmas) involved in this proof in
	 * preorder-traversal order. Used in proof digestion.
	 */
	public List preorderProofs() { return preorder(PROOFS); }

	List preorder(int what) {
		List l = new Vector();
		preorder(l, what);
		return l;
	}

	/**
	 * Verify that the proof steps are indeed valid, and that they
	 * combine as advertised to show the claimed result. If the method
	 * returns without throwing InvalidProofException, the proof was valid.
	 * Subclasses should implement this method to verify the statement
	 * they represent.
	 */
	protected abstract void directVerify()
		throws InvalidProofException;

	/**
	 * Verify that the conclusion this object claims is valid in the
	 * logic of restricted delegation.
	 */
	public void verify()
		throws InvalidProofException {
		if (verifiedOnce) {
			return;
		}
		directVerify();
		verifiedOnce = true;
	}

	/**
	 * Verify that the proof is valid, and that it shows that the
	 * parameter subject speaks for the parameter issuer regarding the
	 * parameter tag.
	 */
	public void verify(SDSIPrincipal issuer, Subject subject, Tag tag)
		throws InvalidProofException {
		if (!subject.equals(getSubject())) {
			throw new InvalidProofException(
				"Proof has different subject than expected.\n"
				+"had: "+Prover2.staticGetName(getSubject())+"\n"
				+"exp: "+Prover2.staticGetName(subject)
				);
		}
		if (!KeyTools.arePrincipalsEquivalent(issuer, getIssuer())) {
			throw new InvalidProofException(
				"Proof has different issuer than expected");
		}
		if (!getTag().hasSubset(tag)) {
			System.out.println("   proof tag: "+getTag().toReadableString(72));
			System.out.println("required tag: "+tag.toReadableString(72));
			System.out.println("intersection: "+getTag().intersect(tag).toReadableString(72));
			throw new InvalidProofException("Proof only valid for a "
				+"(possibly trivial) subset of expected tag");
		}
		verify();
	}

	static final String LABEL = "proof";

	/**
	 * Parse the given SexpList into a Proof object.
	 *
	 * @throws SexpParseException if <CODE>l</CODE> does not represent a Proof
	 * we understand.
	 */
	public static Proof parse(SexpList l)
		throws SexpParseException {
		if (!l.getType().equals(Proof.LABEL)) {
			throw new SexpParseException("This object isn't a proof.");
		}
		String proofType = ((SexpString) l.elementAt(1)).stringContent();
		if (proofType.equals(TwoStepProof.LABEL)) {
			return new TwoStepProof(l);
		}
		if (proofType.equals(SignedCertificateProof.LABEL)) {
			return new SignedCertificateProof(l);
		}
		if (proofType.equals(TrivialProof.LABEL)) {
			return new TrivialProof(l);
		}
		if (proofType.equals(MacProof.LABEL)) {
			return new MacProof(l);
		}
		throw new SexpParseException("Unrecognized proof type "+l.elementAt(1));
	}

	/**
	 * If this proof contains an ith subproof, return it. (i>=0)
	 */
	public Proof getChildProof(int i) {
		return null;
	}

	/**
	 * Substitute the <em>i</em>th subproof of this proof with the supplied
	 * one, returning a new copy of myself (don't change me).
	 * The idea is that we're substituting identical lemmas with
	 * different internal state (already-verified objects representing
	 * the same statement). If we do this substitution after verifying
	 * this object, then we should either clear our own verified flag,
	 * or ensure at substitution time that the new proof's conclusion is
	 * the same as the one we're substituting out.
	 */
	public Proof substituteProof(int i, Proof subproof) {
		return this;
	}
}
