/*
 * 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.commons.infrastructure.remote.filter;

import java.security.PublicKey;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.EnumMap;
import java.util.UUID;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.certificates.cryptoapi.CryptoAPIX509Certificate;
import com.scytl.cryptolib.certificates.factory.CryptoX509Certificate;
import com.scytl.products.ov.commons.infrastructure.config.InfrastructureConfig;
import com.scytl.products.ov.commons.infrastructure.exception.OvCommonsInfrastructureException;
import com.scytl.products.ov.commons.infrastructure.remote.client.NodeIdentifier;
import com.scytl.products.ov.keystore.KeystoreReader;
import com.scytl.products.ov.keystore.KeystoreReaderFactory;

/**
 * @author rmarquez
 * @date 08/09/16 Copyright (C) 2016 Scytl Secure Electronic Voting SA All rights reserved.
 */
public class SignedRequestKeyManager {

    private static final Logger LOG = LoggerFactory.getLogger("std");

    static SignedRequestKeyManager instance = null;

    private static EnumMap<NodeIdentifier, PublicKey> originatorPublicKeyMap = new EnumMap<>(NodeIdentifier.class);

    private static String instanceUUID = "";

    private KeystoreReader keystoreReader;

    /**
     * Singleton private constructor
     */
    private SignedRequestKeyManager() {
    }

    /**
     * Get an instance of this singleton class.
     *
     * @return
     * @throws OvCommonsInfrastructureException
     */
    public static SignedRequestKeyManager getInstance() throws OvCommonsInfrastructureException {
        validateInstance();
        return instance;
    }

    private static void validateInstance() throws OvCommonsInfrastructureException {
        if (instance == null) {
            instance = new SignedRequestKeyManager();
            instance.keystoreReader = new KeystoreReaderFactory().getInstance();
            instanceUUID = UUID.randomUUID().toString();
            InfrastructureConfig.getInstance();
            instance.readKeysFromKeyStorage();
        }
    }

    private void readKeysFromKeyStorage() {
        for (NodeIdentifier nodeIdentifier : NodeIdentifier.values()) {
            // If the map does not contain the key for the originator, it tries to retrieve it.
            if (!originatorPublicKeyMap.containsKey(nodeIdentifier)) {
                PublicKey publicKey = this.getPublicKeyFromCertificate(nodeIdentifier);
                if (publicKey != null)
                    originatorPublicKeyMap.put(nodeIdentifier, publicKey);
            }
        }
    }

    private PublicKey getPublicKeyFromCertificate(NodeIdentifier nodeIdentifier) {
        try {
            Certificate certificateToValidate = this.keystoreReader.readSigningCertificate(nodeIdentifier);

            if (certificateToValidate == null) {
                return null;
            } else {
                CryptoAPIX509Certificate cryptoAPIX509Certificate = getCryptoX509Certificate(certificateToValidate);

                return cryptoAPIX509Certificate.getPublicKey();
            }

        } catch (GeneralCryptoLibException e) {
            LOG.warn("Error trying to retrieve certificate for " + nodeIdentifier.name(), e);
        }
        return null;
    }

    public PublicKey getPublicKeyFromOriginator(NodeIdentifier nodeIdentifier) throws OvCommonsInfrastructureException {
        return originatorPublicKeyMap.get(nodeIdentifier);
    }

    public void setPublicKeyForOriginator(NodeIdentifier nodeIdentifier, PublicKey publicKey)
            throws OvCommonsInfrastructureException {
        originatorPublicKeyMap.put(nodeIdentifier, publicKey);
    }

    public String getInstanceUUID() throws OvCommonsInfrastructureException {
        return instanceUUID;
    }

    public static CryptoX509Certificate getCryptoX509Certificate(final Certificate certificateToValidate)
            throws GeneralCryptoLibException {

        X509Certificate x509Certificate = (X509Certificate) certificateToValidate;

        CryptoX509Certificate cryptoX509Certificate = null;
        try {
            cryptoX509Certificate = new CryptoX509Certificate(x509Certificate);
        } catch (GeneralCryptoLibException e) {
            throw new GeneralCryptoLibException(
                "An error occurred while trying to create a CryptoX509Certificate from the received certificate", e);
        }

        return cryptoX509Certificate;
    }
}
