package com.jscape.inet.ssh.protocol.v2.authentication;

import com.jscape.inet.ssh.protocol.v2.marshaling.keys.*;
import com.jscape.inet.ssh.protocol.v2.marshaling.primitive.StringCodec;
import com.jscape.inet.ssh.protocol.v2.messages.Signature;
import com.jscape.util.crypto.CryptoServicesProvider;
import com.jscape.util.io.ByteArrayOutputStream;
import com.jscape.util.io.DataCodec;
import com.jscape.util.io.primitive.BooleanCodec;

import java.security.KeyPair;
import java.security.Provider;
import java.security.PublicKey;

import static com.jscape.util.Parameters.assertNotNull;

/**
 * @author Alex Usun
 */
public class SignatureService {

    private final DataCodec<PublicKey> publicKeyCodec;
    private final Signer signer;

    public static SignatureService defaultService() {
        return defaultService(CryptoServicesProvider.instance());
    }

    public static SignatureService defaultService(String provider) {
        return new SignatureService(PublicKeyCodecs.init(new PublicKeyCodec(), provider), Signers.init(new ComponentSigner(), provider));
    }

    public static SignatureService defaultService(Provider provider) {
        return new SignatureService(PublicKeyCodecs.init(new PublicKeyCodec(), provider), Signers.init(new ComponentSigner(), provider));
    }

    public SignatureService(DataCodec<PublicKey> publicKeyCodec, Signer signer) {
        assertNotNull(publicKeyCodec);
        this.publicKeyCodec = publicKeyCodec;

        assertNotNull(signer);
        this.signer = signer;
    }

    public Signature signatureFor(byte[] sessionId, String username, String serviceName, String publicKeyAlgorithm, KeyPair keys)
            throws Exception {
        byte[] data = signatureDataFor(sessionId, username, serviceName, publicKeyAlgorithm, keys.getPublic());
        return this.signer.sign(data, publicKeyAlgorithm, keys.getPrivate());
    }

    public void assertSignatureValid(Signature signature, byte[] sessionId, String username, String serviceName, String publicKeyAlgorithm, PublicKey publicKey)
            throws Exception {
        byte[] data = signatureDataFor(sessionId, username, serviceName, publicKeyAlgorithm, publicKey);
        this.signer.assertSignatureValid(data, publicKey, signature);
    }

    private byte[] signatureDataFor(byte[] sessionId, String username, String serviceName, String publicKeyAlgorithm, PublicKey publicKey)
            throws Exception {
        ByteArrayOutputStream buffer = new ByteArrayOutputStream();

        StringCodec.writeValue(sessionId, buffer);
        buffer.write(50);
        StringCodec.writeUtf8Value(username, buffer);
        StringCodec.writeUsAsciiValue(serviceName, buffer);
        StringCodec.writeUsAsciiValue("publickey", buffer);
        BooleanCodec.writeValue(true, buffer);
        StringCodec.writeUsAsciiValue(publicKeyAlgorithm, buffer);
        StringCodec.writeValue(publicKeyBlob(publicKey), buffer);

        return buffer.toByteArray();
    }

    protected byte[] publicKeyBlob(PublicKey key)
            throws Exception {
        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
        this.publicKeyCodec.write(key, buffer);

        return buffer.toByteArray();
    }

}
