package com.jscape.util.keystore;

import com.jscape.util.crypto.CryptoServicesProvider;
import com.jscape.util.io.DataCodec;
import com.jscape.util.io.primitive.IntByteArrayCodec;
import com.jscape.util.io.primitive.IntStringCodec;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.Provider;
import java.security.PublicKey;
import java.security.interfaces.DSAParams;
import java.security.interfaces.DSAPublicKey;
import java.security.spec.DSAPublicKeySpec;

import static com.jscape.util.ExceptionTools.ioException;

/**
 * @author Alex Usun
 */
public class SshDsaPublicKeyCodec
        implements DataCodec<PublicKey> {

    private final Provider provider;

    public static void writeValue(PublicKey value, OutputStream out)
            throws IOException {
        try {
            DSAPublicKey key = (DSAPublicKey) value;
            DSAParams spec = key.getParams();

            IntStringCodec.writeBigEndianValue("ssh-dss", StandardCharsets.US_ASCII, out);
            IntByteArrayCodec.writeBigEndianValue(spec.getP().toByteArray(), out);
            IntByteArrayCodec.writeBigEndianValue(spec.getQ().toByteArray(), out);
            IntByteArrayCodec.writeBigEndianValue(spec.getG().toByteArray(), out);
            IntByteArrayCodec.writeBigEndianValue(key.getY().toByteArray(), out);
        } catch (Exception e) {
            throw ioException(e);
        }
    }

    public static PublicKey readValue(InputStream in, Provider provider)
            throws IOException {
        try {
            String algorithm = IntStringCodec.readBigEndianValue(StandardCharsets.US_ASCII, in);

            assertSupported(algorithm);

            BigInteger p = new BigInteger(IntByteArrayCodec.readBigEndianValue(in));
            BigInteger q = new BigInteger(IntByteArrayCodec.readBigEndianValue(in));
            BigInteger g = new BigInteger(IntByteArrayCodec.readBigEndianValue(in));
            BigInteger y = new BigInteger(IntByteArrayCodec.readBigEndianValue(in));
            DSAPublicKeySpec keySpec = new DSAPublicKeySpec(y, p, q, g);

            KeyFactory factory = provider != null ? KeyFactory.getInstance("DSA", provider) : KeyFactory.getInstance("DSA");

            return factory.generatePublic(keySpec);
        } catch (Exception e) {
            throw ioException(e);
        }
    }

    private static void assertSupported(String algorithm)
            throws IOException {
        if (!algorithm.equals("ssh-dss")) {
            throw new IOException("Unsupported algorithm: " + algorithm);
        }
    }

    public static SshDsaPublicKeyCodec defaultCodec() {
        return new SshDsaPublicKeyCodec(null);
    }

    public static SshDsaPublicKeyCodec bcCodec() {
        return new SshDsaPublicKeyCodec(CryptoServicesProvider.instance());
    }

    public SshDsaPublicKeyCodec(Provider provider) {
        this.provider = provider;
    }

    public void write(PublicKey value, OutputStream out)
            throws IOException {
        writeValue(value, out);
    }

    public PublicKey read(InputStream in)
            throws IOException {
        return readValue(in, this.provider);
    }

}