/*
 * 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 com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.api.primitives.PrimitivesServiceAPI;
import com.scytl.cryptolib.elgamal.bean.ElGamalPublicKey;
import com.scytl.cryptolib.mathematical.groups.impl.ZpGroupElement;
import com.scytl.products.ov.config.exceptions.specific.FingerprintGeneratorException;
import com.scytl.products.ov.constants.Constants;
import org.apache.commons.codec.binary.Base64;

import java.nio.charset.StandardCharsets;
import java.security.PublicKey;
import java.util.List;

/**
 * Allows the generation of a fingerprint (a hash) for various structures.
 * <P>
 * Uses the hash algorithm that is configured within the CryptoLib properties file. The generated hash is encoded in
 * Base64. Supports the following structures as input:
 * <ul>
 * <li>
 * {@link java.security.PublicKey}.</li>
 * <li>
 * {@link com.scytl.cryptolib.elgamal.bean.ElGamalPublicKey}.</li>
 * <li>byte array.</li>
 * <li>
 * {@link java.lang.String}.</li>
 * </ul>
 */
public final class FingerprintGenerator {

    private final PrimitivesServiceAPI _primitiveService;

    public FingerprintGenerator(final PrimitivesServiceAPI primitiveService) {

        validateInput(primitiveService);

        _primitiveService = primitiveService;
    }

    /**
     * Generate a fingerprint of the received {@link java.security.PublicKey}.
     *
     * @param publicKey
     *            the public key for which to generate a fingerprint.
     * @return The fingerprint of the received public key.
     */
    public String generate(final PublicKey publicKey) {

        validateInput(publicKey);

        final byte[] publicKeyAsBytes = publicKey.getEncoded();

        byte[] hashOfPublicKey;
        try {
            hashOfPublicKey = _primitiveService.getHash(publicKeyAsBytes);
        } catch (GeneralCryptoLibException e) {
            throw new FingerprintGeneratorException("Error while trying to generate hash of public key", e);
        }

        return new String(Base64.encodeBase64(hashOfPublicKey), StandardCharsets.UTF_8);
    }

    /**
     * Generate a fingerprint of the received {@link com.scytl.cryptolib.elgamal.bean.ElGamalPublicKey}.
     *
     * @param publicKey
     *            the public key for which to generate a fingerprint.
     * @return The fingerprint of the received public key.
     */
    public String generate(final ElGamalPublicKey publicKey) {

        validateInput(publicKey);

        String publicKeyAsString = getElGamalPublicKeyAsString(publicKey);
        
        byte[] hashOfPublicKey;
        try {
            hashOfPublicKey = _primitiveService.getHash(publicKeyAsString.getBytes(StandardCharsets.UTF_8));
        } catch (GeneralCryptoLibException e) {
            throw new FingerprintGeneratorException("Error while trying to generate hash of public key", e);
        }

        return new String(Base64.encodeBase64(hashOfPublicKey), StandardCharsets.UTF_8);
    }

    /**
     * Generate a fingerprint of the received byte array.
     *
     * @param bytes
     *            the byte array for which to generate a fingerprint.
     * @return The fingerprint of the received byte array.
     */
    public String generate(final byte[] bytes) {

        validateInput(bytes);

        byte[] hashOfBytes;
        try {
            hashOfBytes = _primitiveService.getHash(bytes);
        } catch (GeneralCryptoLibException e) {
            throw new FingerprintGeneratorException("Error while trying to generate hash of the received bytes", e);
        }

        return new String(Base64.encodeBase64(hashOfBytes), StandardCharsets.UTF_8);
    }

    /**
     * Generate a fingerprint of the received String.
     *
     * @param data
     *            the string for which to generate a fingerprint.
     * @return The fingerprint of the received byte array.
     */
    public String generate(final String data) {

        validateInput(data);

        byte[] hashOfBytes;
        try {
            hashOfBytes = _primitiveService.getHash(data.getBytes(StandardCharsets.UTF_8));
        } catch (GeneralCryptoLibException e) {
            throw new FingerprintGeneratorException("Error while trying to generate hash of the received bytes", e);
        }

        return new String(Base64.encodeBase64(hashOfBytes), StandardCharsets.UTF_8);
    }

    private void validateInput(final String data) {

        if (data == null) {
            throw new IllegalArgumentException("The received string is null");
        }
    }

    private void validateInput(final byte[] bytes) {

        if ((bytes == null) || (bytes.length < 1)) {
            throw new IllegalArgumentException("The received byte array must be an initialized non-empty array");
        }
    }

    private void validateInput(final ElGamalPublicKey publicKey) {

        if (publicKey == null) {
            throw new IllegalArgumentException("The received ElGamal publicKey is null");
        }
    }

    private void validateInput(final PublicKey publicKey) {

        if (publicKey == null) {
            throw new IllegalArgumentException("The received publicKey is null");
        }
    }

    private void validateInput(final PrimitivesServiceAPI primitiveService) {

        if (primitiveService == null) {
            throw new IllegalArgumentException("The received primitives service is null");
        }
    }

    private String getElGamalPublicKeyAsString(final ElGamalPublicKey publicKey) {

        List<ZpGroupElement> pubicKeys = publicKey.getKeys();

        StringBuilder publicKeyStringBuilder = new StringBuilder();

        publicKeyStringBuilder.append(getZpGroupElementAsString(pubicKeys.get(0)));
        for (int i = 1; i < pubicKeys.size(); i++) {
            publicKeyStringBuilder.append(Constants.ELEMENTS_DELIMITER);
            publicKeyStringBuilder.append(getZpGroupElementAsString(pubicKeys.get(i)));
        }

        return publicKeyStringBuilder.toString();
    }
    
    private String getZpGroupElementAsString(ZpGroupElement element) {
        StringBuilder builder = new StringBuilder();
        builder.append(element.getP()).append(element.getQ()).append(element.getValue());
        return builder.toString();
    }
}
