package org.apache.sshd.common.config.keys.impl;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.KeyPairGenerator;
import java.security.interfaces.EdECPrivateKey;
import java.security.interfaces.EdECPublicKey;
import java.security.spec.EdECPoint;
import java.security.spec.EdECPrivateKeySpec;
import java.security.spec.EdECPublicKeySpec;
import java.security.spec.NamedParameterSpec;
import java.util.Collections;
import java.util.Map;
import org.apache.sshd.common.keyprovider.KeyPairProvider;
import org.apache.sshd.common.session.SessionContext;
import org.apache.sshd.common.util.ArrayUtil;

public final class Ed25519PublicKeyEntryDecoder extends AbstractPublicKeyEntryDecoder<EdECPublicKey, EdECPrivateKey> {

    public static final Ed25519PublicKeyEntryDecoder INSTANCE = new Ed25519PublicKeyEntryDecoder();

    private Ed25519PublicKeyEntryDecoder() {
        super(EdECPublicKey.class, EdECPrivateKey.class, Collections.singletonList(KeyPairProvider.SSH_ED25519));
    }

    @Override
    public KeyPairGenerator getKeyPairGenerator() throws GeneralSecurityException {
        return KeyPairGenerator.getInstance("Ed25519");
    }

    @Override
    public String encodePublicKey(OutputStream s, EdECPublicKey key) throws IOException {
        throw new UnsupportedOperationException();
    }

    @Override
    public KeyFactory getKeyFactoryInstance() throws GeneralSecurityException {
        return KeyFactory.getInstance("EdDSA");
    }

    @Override
    public EdECPublicKey decodePublicKey(SessionContext session,
                                         String keyType,
                                         InputStream keyData,
                                         Map<String, String> headers)
            throws IOException, GeneralSecurityException {
        byte[] arr = keyData.readAllBytes();
        byte msb = arr[arr.length - 1];
        boolean xOdd = (msb & 0x80) != 0;
        arr[arr.length - 1] &= (byte) 0x7F;
        ArrayUtil.reverse(arr);
        BigInteger y = new BigInteger(1, arr);
        NamedParameterSpec namedParameterSpec = NamedParameterSpec.ED25519;
        EdECPoint edECPoint = new EdECPoint(xOdd, y);
        return generatePublicKey(new EdECPublicKeySpec(namedParameterSpec, edECPoint));
    }

    @Override
    public EdECPublicKey clonePublicKey(EdECPublicKey key) throws GeneralSecurityException {
        if (key == null) {
            return null;
        } else {
            NamedParameterSpec namedParameterSpec = NamedParameterSpec.ED25519;
            EdECPoint edECPoint = key.getPoint();
            return generatePublicKey(new EdECPublicKeySpec(namedParameterSpec, edECPoint));
        }
    }

    @Override
    public EdECPrivateKey clonePrivateKey(EdECPrivateKey key) throws GeneralSecurityException {
        if (key == null) {
            return null;
        } else {
            NamedParameterSpec namedParameterSpec = NamedParameterSpec.ED25519;
            return generatePrivateKey(new EdECPrivateKeySpec(namedParameterSpec, key.getEncoded()));
        }
    }
}
