package aalg.crypto;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;

import aalg.algebra.Utils;

/**
 * Establisher of anonymous authentication protocol.
 * @author Ing. Petr Ležák
 *
 * @param <PRIV> type of private key
 * @param <PUB> type of public key
 */
public class AnonymousEstablisher<PRIV extends IPrivateKey, PUB extends IPublicKey> extends AnonymousBase {
	
	private final IKeyGenerator<PRIV, PUB> keyGenerator;
	private final IAssymetricKeyIo<PRIV, PUB> keyIo;
	private final ISigner<PRIV> signer;
	private final IAssymetricCipher<PRIV, PUB> assymetricCipher;
	private final PRIV ownPrivateKey;
	private final long ownId;
	
	public AnonymousEstablisher(final IKeyGenerator<PRIV, PUB> keyGenerator, final IAssymetricKeyIo<PRIV, PUB> keyIo, final ISigner<PRIV> signer, final IAssymetricCipher<PRIV, PUB> assymetricCipher, final long ownId, final PRIV ownPrivateKey) {
		this.keyGenerator = keyGenerator;
		this.keyIo = keyIo;
		this.signer = signer;
		this.assymetricCipher = assymetricCipher;
		this.ownId = ownId;
		this.ownPrivateKey = ownPrivateKey;
	}
	
	public AnonymousVerificationValues<PUB> generateVerificationValues (final long applicantId, final PUB applicantPublicKey) throws IOException {
		keyGenerator.generateKeys();
		
		final PRIV tmpPrivKey = keyGenerator.getPrivateKey();
		final PUB tmpPubKey = keyGenerator.getPublicKey();
		
		final byte[] message = createMessage (applicantId, ownId, tmpPrivKey);
		final ISignature signature = signer.sign(message, ownPrivateKey);
		final byte[] encryptedMessage = encryptMessage (message, signature, applicantPublicKey);

		return new AnonymousVerificationValues<PUB>(encryptedMessage, tmpPubKey);
	}

	private byte[] createMessage(final long idApp, final long idEst, final PRIV tmpPrivKey) throws IOException {
		final ByteArrayOutputStream stream = new ByteArrayOutputStream();
		Utils.writeLong(idApp, stream, Utils.LONG_BYTE_COUNT);
		Utils.writeLong(idEst, stream, Utils.LONG_BYTE_COUNT);
		keyIo.writePrivateKey(tmpPrivKey, stream);
		
		return stream.toByteArray();
	}
	
	private byte[] encryptMessage(final byte[] message, final ISignature signature, final PUB applicantPublicKey) throws IOException {
		final ByteArrayOutputStream targetStream = new ByteArrayOutputStream();
		assymetricCipher.setPublicKey(applicantPublicKey);
		
		final IStreamEncryptor encryptor = assymetricCipher.getEncryptor();
		encryptor.initialize(targetStream);
		
		final OutputStream sourceStream = encryptor.getSourceStream();
		Utils.writeByteArrayWithSize(sourceStream, message, MESSAGE_LENGTH_BYTES);
		signature.writeToStream (sourceStream);
		
		encryptor.finish();
		
		return targetStream.toByteArray();
	}

}
