package proof;

import Tools.*;
import sdsi.*;
import sdsi.sexp.*;
import java.io.*;
import java.security.*;

/**
 * A batch of tools related to parsing S-expressions from files and
 * streams, and parsing
 * from S-expressions {@link Proof}s, {@link SDSIKeyPair}s, and other
 * Snowflake extensions to Morcos' {@link sdsi} package.
 */
public class KeyTools {

	/**
	 * Parse a public key out of a Unix file
	 */
	public static SDSIPublicKey getPublicKey(String filename)
		throws FileNotFoundException {
		SDSIObject o = processFilename(filename+".public");
		Tools.Assert.assert(o!=null && o instanceof SDSIPublicKey);
		return (SDSIPublicKey) o;
	}

	/**
	 * Parse a private key out of a Unix file
	 */
	public static SDSIPrivateKey getPrivateKey(String filename)
		throws FileNotFoundException {
		SDSIObject o = processFilename(filename+".private");
		Tools.Assert.assert(o!=null && o instanceof SDSIPrivateKey);
		return (SDSIPrivateKey) o;
	}

	/**
	 * Parse a general SDSIObject (including Snowflake extensions) out
	 * of a Unix file.
	 *
	 * @param file Java File object pointing at the file.
	 */
	public static SDSIObject processFile(File file)
		throws FileNotFoundException {
		PushbackInputStream pis =
			new PushbackInputStream(
				new BufferedInputStream(
					new FileInputStream(file)));
		return processStream(pis);
	}

	/**
	 * Parse a general SDSIObject (including Snowflake extensions) out
	 * of a Unix file.
	 *
	 * @param filename String path name of the file.
	 */
	public static SDSIObject processFilename(String filename)
		throws FileNotFoundException {
		return processFile(new File(filename));
	}

	/**
	 * Parse a general SDSIObject (including Snowflake extensions) out
	 * of a byte buffer.
	 */
	public static SDSIObject parseBytes(byte[] buf) {
		PushbackInputStream pis = new PushbackInputStream(
			new ByteArrayInputStream(buf));
		return processStream(pis);
	}

	/**
	 * Parse a general SDSIObject (including Snowflake extensions) out
	 * of a String.
	 */
	public static SDSIObject parseString(String str) {
		PushbackInputStream pis = new PushbackInputStream(
			new ByteArrayInputStream(str.getBytes()));
		return processStream(pis);
	}

	static SDSIObject processStream(PushbackInputStream pbis) {
		try {
//			timingexp.Timeline.zeroTimer();
//			timingexp.Timeline.timePoint("start process");
			Sexp sexp = Sexp.parse(pbis);
//			timingexp.Timeline.timePoint("sexp parsed");
			SDSIObject sobj = parseSexp(sexp);
//			timingexp.Timeline.timePoint("SDSIobject parsed");
			return sobj;
		} catch (SexpException ex) {
			System.out.println(ex);
		} catch (SexpParseException ex) {
			System.out.println(ex);
		}
		System.out.println("returning null");
		// maybe throw an exception here?
		return null;
	}

	/**
	 * Basically, an extension to SDSIObject.principalParse that
	 * understands more object types.
	 */
	static SDSIObject parseSexp(Sexp sexp)
		throws SexpParseException {
		if (sexp == null) {
			throw new SexpParseException("sexp null");
		} else if (sexp instanceof SexpList) {
			SexpList slist = (SexpList) sexp;

			// have to do some of our own object-type dispatch
			// here, because principalParse doesn't know about our
			// nifty extended types.
			// (well, could be factored better; doesn't have to be *here*!)
			SDSIObject sobject;
			if (slist.getType().equals(SignedCertificate.LABEL)) {
				sobject = new SignedCertificate(slist);
			} else if (slist.getType().equals(Tag.LABEL)) {
				sobject = new Tag(slist);
			} else if (slist.getType().equals(SDSIKeyPair.LABEL)) {
				sobject = new SDSIKeyPair(slist);
			} else if (slist.getType().equals(Proof.LABEL)) {
				sobject = Proof.parse(slist);
			} else if (slist.getType().equals(Quoting.LABEL)) {
				sobject = new Quoting(slist, null);
			} else if (slist.getType().equals(ThresholdSubject.LABEL)) {
				sobject = new ThresholdSubject(slist, null);
			} else {
				sobject = SDSIObject.principalParse(slist, null);
			}
				// key is in its own context
			return sobject;
		} else {
			throw new SexpParseException("sexp isn't SexpList, it's "
				+sexp.getClass());
		}
	}

	/**
	 * Check for equivalence up to hash. Can't tell if two different
	 * hashes are equivalent, of course, but can help when one is a
	 * principal and the other is its hash.
	 */
	public static boolean arePrincipalsEquivalent(SDSIObject subjectObj,
		SDSIObject issuerObj) {
		if (issuerObj.equals(subjectObj)) {
			return true;
		}
		SDSIPrincipal subject, issuer;
		if (subjectObj instanceof SDSIPrincipal) {
			subject = (SDSIPrincipal) subjectObj;
		} else {
			return false;
		}
		if (issuerObj instanceof SDSIPrincipal) {
			issuer = (SDSIPrincipal) issuerObj;
		} else {
			return false;
		}
		try {
			if (issuer instanceof SDSIPublicKey
				&& subject instanceof Hash) {
				Hash iHash = new Hash(((Hash) subject).getHashID(),
					issuer);
				return iHash.equals(subject);
			}
			if (subject instanceof SDSIPublicKey
				&& issuer instanceof Hash) {
				Hash sHash = new Hash(((Hash) issuer).getHashID(),
					(SDSIPublicKey) subject);
				return sHash.equals(issuer);
			}
		} catch (NoSuchAlgorithmException ex) {
			// okay, then we can't really check this hash, can we?
		}
		return false;
	}

	/**
	 * Hash a bytestream and return a SPKI ``ObjectHash,'' a principal
	 * that identifies that particular bag of bytes.
	 *
	 * @param object the array of bytes to hash.
	 */
	public static ObjectHash hashObject(byte[] object) {
		MessageDigest md;
		try {
			md = MessageDigest.getInstance("md5");
		} catch (NoSuchAlgorithmException ex) {
			return null;
		}
		md.update(object);
		return new ObjectHash(new Hash("md5", md.digest()));
	}

	/**
	 * Hash a bytestream and return a SPKI ``ObjectHash,'' a principal
	 * that identifies that particular bag of bytes.
	 *
	 * @param inStream the stream of bytes to hash.
	 */
	public static ObjectHash hashStream(InputStream inStream) {
		MessageDigest md;
		try {
			md = MessageDigest.getInstance("md5");
			DigestOutputStream dos = new DigestOutputStream(
				new NullOutputStream(), md);
			CopyStream.copyStream(inStream, dos);
			return new ObjectHash(new Hash("md5", md.digest()));
		} catch (NoSuchAlgorithmException ex) {
		} catch (IOException ex) {
		}
		return null;
	}
}
