package aalg.crypto;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import aalg.algebra.Utils;

/**
 * Applicant of anonymous authentication protocol.
 * @author Ing. Petr Ležák
 *
 * @param <SIGN> type of signature
 * @param <PRIV> type of private key
 * @param <PUB> type of public key
 */
public class AnonymousApplicant<SIGN extends ISignature, PRIV extends IPrivateKey, PUB extends IPublicKey> extends AnonymousBase {
	private final IAssymetricKeyIo<PRIV, PUB> keyIo;
	private final IPublicKeyRegister<PUB> publicKeyRegister;
	private final ISignatureVerifier<SIGN, PUB> verifier;
	private final IAssymetricCipher<PRIV, PUB> assymetricCipher;
	private final PRIV ownPrivateKey;
	
	private static class MessageWithSignature<SIGN extends ISignature> {
		final byte[] message;
		final SIGN signature;
		
		public MessageWithSignature (final byte[] message, final SIGN signature) {
			this.message = message;
			this.signature = signature;
		}
	}
	
	public AnonymousApplicant(final IAssymetricKeyIo<PRIV, PUB> keyIo, final IPublicKeyRegister<PUB> publicKeyRegister, final ISignatureVerifier<SIGN, PUB> verifier, final IAssymetricCipher<PRIV, PUB> assymetricCipher, final PRIV ownPrivateKey) {
		this.keyIo = keyIo;
		this.publicKeyRegister = publicKeyRegister;
		this.verifier = verifier;
		this.assymetricCipher = assymetricCipher;
		this.ownPrivateKey = ownPrivateKey;
	}
	
	public byte[] calculateResponse (final AnonymousChallenge challenge) throws IOException {
		final byte[] encryptedMessage = challenge.getEncryptedMessage();
		final MessageWithSignature<SIGN> messageWithSignature = decryptMessage(encryptedMessage);
		
		final ByteArrayInputStream messageStream = new ByteArrayInputStream(messageWithSignature.message);
		final long idApp = Utils.readLong(messageStream, Utils.LONG_BYTE_COUNT);
		final long idEst = Utils.readLong(messageStream, Utils.LONG_BYTE_COUNT);
		final PRIV keyPriv = keyIo.readPrivateKey(messageStream);
		final PUB establisherPublicKey = publicKeyRegister.getPublicKey(idEst);
		
		if (!verifier.verify(messageWithSignature.message, messageWithSignature.signature, establisherPublicKey))
			throw new RuntimeException("Wrong signature");
		
		final byte[] decryptedChallenge = decryptChallengeAndVerifyInfo(challenge.getChallenge(), keyPriv);
		
		return decryptedChallenge;
	}

	private MessageWithSignature<SIGN> decryptMessage(final byte[] encryptedMessage) throws IOException {
		final ByteArrayInputStream sourceStream = new ByteArrayInputStream(encryptedMessage);
		assymetricCipher.setPrivateKey(ownPrivateKey);
		
		final IStreamDecryptor decryptor = assymetricCipher.getDecryptor();
		decryptor.initialize(sourceStream);
		
		final InputStream targetStream = decryptor.getTargetStream();
		final byte[] message = Utils.readByteArrayWithSize(targetStream, MESSAGE_LENGTH_BYTES);

		final SIGN signature = verifier.readSignatureFromStream (targetStream);
		Utils.verifyEndOfStream(targetStream);
		
		decryptor.finish();
		
		return new MessageWithSignature<SIGN>(message, signature);
	}

	private byte[] decryptChallengeAndVerifyInfo(final byte[] encryptedChallenge, final PRIV challengeKey) throws IOException {
		final ByteArrayInputStream sourceStream = new ByteArrayInputStream(encryptedChallenge);
		assymetricCipher.setPrivateKey(challengeKey);
		
		final IStreamDecryptor decryptor = assymetricCipher.getDecryptor();
		decryptor.initialize(sourceStream);
		
		final InputStream targetStream = decryptor.getTargetStream();
		final byte[] response = Utils.readByteArrayWithSize(targetStream, RESPONSE_LENGTH_BYTES);
		final byte[] infoBytes = Utils.readByteArrayWithSize(targetStream, INFO_LENGTH_BYTES);		
		final String info = new String(infoBytes, INFO_CHARSET);
		
		decryptor.finish();
		
		return response;
	}

}
