/*
 * Copyright 2018 Scytl Secure Electronic Voting SA
 *
 * All rights reserved
 *
 * See our extended copyright notice in *file 'Copyright.txt' which is part of this source code package
 */
package com.scytl.cryptolib.scytl.keystore.tests;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.Security;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;

import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.junit.AfterClass;
import org.junit.BeforeClass;

import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.elgamal.bean.ElGamalEncryptionParameters;
import com.scytl.cryptolib.elgamal.bean.ElGamalPrivateKey;
import com.scytl.cryptolib.elgamal.bean.ElGamalPublicKey;
import com.scytl.cryptolib.elgamal.factory.ElGamalFactory;
import com.scytl.cryptolib.mathematical.groups.impl.Exponent;
import com.scytl.cryptolib.mathematical.groups.impl.ZpGroupElement;
import com.scytl.cryptolib.mathematical.groups.impl.ZpSubgroup;
import com.scytl.cryptolib.mathematical.groups.utils.GroupUtils;
import com.scytl.cryptolib.primitives.derivation.factory.CryptoPBKDFDeriver;
import com.scytl.cryptolib.primitives.messagedigest.factory.CryptoMessageDigest;
import com.scytl.cryptolib.primitives.securerandom.factory.CryptoRandomInteger;
import com.scytl.cryptolib.symmetric.cipher.factory.CryptoSymmetricCipher;
import com.scytl.cryptolib.symmetric.cipher.factory.SymmetricCipherFactory;
import com.scytl.cryptolib.symmetric.key.configuration.ConfigSecretKeyAlgorithmAndSpec;

public class BaseScytlKeyStoreTests {

    protected static ElGamalEncryptionParameters _elGamalEncryptionParameters;

    protected static ElGamalPublicKey _elGamalPublicKeyEB;

    protected static ElGamalPrivateKey _elGamalPrivateKeyEB;

    protected static ElGamalPublicKey _elGamalPublicKeyVS;

    protected static ElGamalPrivateKey _elGamalPrivateKeyVS;

    protected static ZpSubgroup _group;

    protected static CryptoPBKDFDeriver _cryptoPBKDFDeriver;

    protected static SymmetricCipherFactory _symmetricCipherFactory;

    protected static ElGamalFactory _elGamalFactory;

    protected static ConfigSecretKeyAlgorithmAndSpec _configSecretKeyAlgorithmAndSpec;

    protected static CryptoMessageDigest _cryptoMessageDigest;

    protected static String _votingOptions;

    protected static String _receipt;

    protected ZpGroupElement _symmetricKey;

    protected static byte[] _hashReceiptNumber;

    protected Exponent _randomExtension;

    protected static GroupUtils _ZpSubgroupUtils;

    protected static CryptoRandomInteger _cryptoRandomInteger;

    protected static CryptoSymmetricCipher _symmetricCipher;

    protected static String _symmetricallyEncryptedDataBase64;

    protected static String _asymmetricallyEncryptedSecretKey;

    public static X509Certificate loadX509Certificate(
            final String fileName) throws GeneralSecurityException, IOException {
        CertificateFactory cf = CertificateFactory.getInstance("X.509");
        try (InputStream is = getResourceAsStream(fileName)) {
            return (X509Certificate) cf.generateCertificate(is);
        }
    }

    public static PrivateKey loadPrivateKey(final String fileName)
            throws GeneralSecurityException, IOException {
        byte[] keyBytes = new byte[1679];
        try (InputStream is = getResourceAsStream(fileName)) {
            is.read(keyBytes);
        }
        String privateKey = new String(keyBytes, StandardCharsets.UTF_8);
        privateKey = privateKey.replaceAll(
            "(-+BEGIN RSA PRIVATE KEY-+\\r?\\n|-+END RSA PRIVATE KEY-+\\r?\\n?)",
            "");
        keyBytes = Base64.getMimeDecoder().decode(privateKey);

        // generate private key
        PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        return keyFactory.generatePrivate(spec);
    }

    public static SecretKey loadSecretKey(final String fileName)
            throws GeneralSecurityException, IOException {
        byte[] keyBytes = new byte[16];
        try (InputStream is = getResourceAsStream(fileName)) {
            is.read(keyBytes);
        }
        // generate private key
        return new SecretKeySpec(keyBytes, 0, keyBytes.length, "AES");
    }

    public static ElGamalPrivateKey loadElGamalPrivateKey(
            final String fileName)
            throws GeneralSecurityException, IOException,
            GeneralCryptoLibException {
        StringBuilder json = new StringBuilder();
        try (InputStream is = getResourceAsStream(fileName);
                Reader reader =
                    new InputStreamReader(is, StandardCharsets.UTF_8)) {
            int c;
            while ((c = reader.read()) != -1) {
                json.append((char) c);
            }
        }
        return ElGamalPrivateKey.fromJson(json.toString());
    }

    @BeforeClass
    public static void setUp()
            throws GeneralCryptoLibException, GeneralSecurityException,
            IOException {

        Security.addProvider(new BouncyCastleProvider());

    }

    @AfterClass
    public static void removeBouncyCastleProvider() {
        // here Method getProviderName() throws
        // 'java.lang.IllegalStateException' exception.
        Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME);
    }

    protected ZpGroupElement getRandomGroupElement(
            final GroupUtils ZpSubgroupUtils, final ZpSubgroup group,
            final ZpGroupElement generator,
            final CryptoRandomInteger cryptoRandomInteger)
            throws GeneralCryptoLibException {

        return new ZpGroupElement(generator.exponentiate(
            ZpSubgroupUtils.getRandomExponent(group, cryptoRandomInteger))
            .getValue(), group);
    }

    protected void createSecretKeyAndRandomExtension()
            throws GeneralCryptoLibException {
        ZpGroupElement generator = new ZpGroupElement(
            _elGamalEncryptionParameters.getG(), _group);

        _symmetricKey = getRandomGroupElement(_ZpSubgroupUtils, _group,
            generator, _cryptoRandomInteger);

        _randomExtension = _ZpSubgroupUtils.getRandomExponent(
            _elGamalEncryptionParameters.getGroup(), _cryptoRandomInteger);
    }
    
    private static InputStream getResourceAsStream(String name) {
        return BaseScytlKeyStoreTests.class
            .getResourceAsStream('/' + name);
    }
}
