/*
 * 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.products.ov.utils;

import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;

import com.scytl.products.ov.config.exceptions.ConfigurationEngineException;
import com.scytl.products.ov.constants.Constants;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.jcajce.JcaPEMWriter;
import org.bouncycastle.pkcs.PKCS10CertificationRequest;
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest;



public final class PemUtils {

	/**
     * Non-public constructor
     */
	private PemUtils() {
    }

    private static final String PEM_HEADER_PREFIX = "-----BEGIN";

    private static final String PEM_FOOTER_PREFIX = "-----END";

    private static final String NEWLINE = "\n";

    private static final String RETURN = "\r";

    private static final String EMPTY_STRING = "";

    /**
     * Converts a {@link org.bouncycastle.pkcs.PKCS10CertificationRequest} to PEM format.
     *
     * @param csr
     *            the {@link org.bouncycastle.pkcs.PKCS10CertificationRequest} to convert.
     * @return the {@link org.bouncycastle.pkcs.PKCS10CertificationRequest} in PEM format.
     * @throws com.scytl.products.ov.config.exceptions.ConfigurationEngineException
     *             if the input validation or the object to PEM conversion fails.
     */
    public static final String csrToPem(final PKCS10CertificationRequest csr) throws ConfigurationEngineException {

        validateInputObject(csr);

        return toPem(csr);
    }

    public static final JcaPKCS10CertificationRequest csrFromPem(final String pemStr)
            throws ConfigurationEngineException, IOException {

        validateInputPem(pemStr, Constants.CSR_BEGIN_STRING);

        PEMParser pemParser = null;
        try {

            pemParser = new PEMParser(new StringReader(formatPemString(pemStr, Constants.CSR_BEGIN_STRING)));

            Object parsedObj = pemParser.readObject();

            JcaPKCS10CertificationRequest jcaPKCS10CertificationRequest = null;
            if (parsedObj instanceof PKCS10CertificationRequest) {
                jcaPKCS10CertificationRequest =
                    new JcaPKCS10CertificationRequest((PKCS10CertificationRequest) parsedObj);
            }

            return jcaPKCS10CertificationRequest;
        } catch (Exception e) {
            throw new ConfigurationEngineException("Error while trying to create a CSR object from PEM file", e);
        } finally {
            if (pemParser != null) {
                pemParser.close();
            }
        }
    }

    private static final void validateInputObject(final Object obj) throws ConfigurationEngineException {

        if (obj == null) {
            throw new ConfigurationEngineException("The cryptographic object provided as input is null.",
                new IllegalArgumentException());
        }
    }

    private static final String toPem(final Object T) throws ConfigurationEngineException {

        try {
            StringWriter stringWriter = new StringWriter();
            JcaPEMWriter pemWriter = new JcaPEMWriter(stringWriter);
            String pemStr;

            pemWriter.writeObject(T);
            pemWriter.flush();
            stringWriter.flush();
            pemStr = stringWriter.toString();
            stringWriter.close();
            pemWriter.close();

            return pemStr;
        } catch (IOException e) {
            throw new ConfigurationEngineException("Could not convert object of type " + T + " to PEM format.", e);
        }
    }

    private static final void validateInputPem(final String pemStr, final String headerSuffix) throws ConfigurationEngineException {

        if (pemStr == null) {
            throw new ConfigurationEngineException("The PEM string provided as input is null.", new IllegalArgumentException());
        }

        if (pemStr.isEmpty()) {
            throw new ConfigurationEngineException("The PEM string provided as input is empty.", new IllegalArgumentException());
        }

        if (!pemStr.contains(PEM_HEADER_PREFIX) || !pemStr.contains(headerSuffix)) {
            throw new ConfigurationEngineException("The PEM string does not contain a valid header.", new IllegalArgumentException());
        }

        if (!pemStr.contains(PEM_FOOTER_PREFIX)) {
            throw new ConfigurationEngineException("The PEM string does not contain a valid footer.", new IllegalArgumentException());
        }
    }

    private static String formatPemString(final String pemStr, final String headerSuffix) {

        String formattedPemStr = removeOsDependentSymbols(pemStr);

        formattedPemStr = formattedPemStr.replaceFirst(headerSuffix, headerSuffix + NEWLINE);

        formattedPemStr = formattedPemStr.replaceFirst(PEM_FOOTER_PREFIX, NEWLINE + PEM_FOOTER_PREFIX);

        return formattedPemStr;
    }

    private static String removeOsDependentSymbols(final String pemStr) {

        return pemStr.replace(NEWLINE, EMPTY_STRING).replace(RETURN, EMPTY_STRING);
    }
}
