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

import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.util.List;

import com.scytl.cryptolib.api.exceptions.CryptoLibException;
import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.commons.configuration.Provider;
import com.scytl.cryptolib.commons.utils.validations.Validate;
import com.scytl.cryptolib.mathematical.groups.GroupElement;
import com.scytl.cryptolib.mathematical.groups.impl.ZpGroupElement;
import com.scytl.cryptolib.primitives.messagedigest.configuration.ConfigMessageDigestAlgorithmAndProvider;
import com.scytl.cryptolib.proofs.maurer.configuration.ConfigProofHashCharset;

/**
 * Utility class which provides high-level functions for obtaining a hash value
 * from a group of Objects. This class makes use of a standard hash function,
 * which is set when instances of this class are created.
 */
public final class HashBuilder {

    private final MessageDigest _md;

    private final ConfigProofHashCharset _charsetName;

    /**
     * Creates an instance of {@code HashBuilder} and initialized it by provided
     * values.
     * 
     * @param configMessageDigestAlgorithmAndProvider
     *            the message digest policy.
     * @param charset
     *            the charset policy.
     */
    public HashBuilder(
            final ConfigMessageDigestAlgorithmAndProvider configMessageDigestAlgorithmAndProvider,
            final ConfigProofHashCharset charset) {

        try {
            if (Provider.DEFAULT == configMessageDigestAlgorithmAndProvider
                .getProvider()) {
                _md = MessageDigest
                    .getInstance(configMessageDigestAlgorithmAndProvider
                        .getAlgorithm());
            } else {
                _md = MessageDigest.getInstance(
                    configMessageDigestAlgorithmAndProvider.getAlgorithm(),
                    configMessageDigestAlgorithmAndProvider.getProvider()
                        .getProviderName());
            }
        } catch (GeneralSecurityException e) {
            throw new CryptoLibException(
                "Failed to create message digest generator for the proofs. "
                    + e.getMessage(),
                e);
        }

        _charsetName = charset;
    }

    /**
     * Get the name of the charset encoding system used by this class. This
     * charset encoding system is used when converting Strings to arrays of
     * bytes, which is done when a hash is generating using the String
     * representation of an Object.
     *
     * @return a charset name.
     */
    public ConfigProofHashCharset getCharset() {
        return _charsetName;
    }

    /**
     * Generates a hash value from the
     * {@link com.scytl.cryptolib.mathematical.groups.GroupElement#getValue()}
     * and string value of {@code data}.
     *
     * @param publicValues
     *            group elements representing public values.
     * @param generatedValues
     *            group elements representing generated values.
     * @param data
     *            some data encoded as a {@code String}.
     * @return the generated hash value.
     * @throws GeneralCryptoLibException
     *             if the list of public or generated group elements is null,
     *             empty or contains one or more null elements, or if the data
     *             encoded as a string is null or blank.
     */
    public byte[] buildHashForProofs(
            final List<? extends GroupElement<?>> publicValues,
            final List<? extends GroupElement<?>> generatedValues,
            final String data) throws GeneralCryptoLibException {

        Validate.notNullOrEmptyAndNoNulls(publicValues,
            "List of public group elements");
        Validate.notNullOrEmptyAndNoNulls(generatedValues,
            "List of generated group elements");
        Validate.notNullOrBlank(data, "Data encoded as string");

        updateMessageDigestWith(publicValues);
        updateMessageDigestWith(generatedValues);
        updateMessageDigestWith(data);
        return _md.digest();
    }

    private void updateMessageDigestWith(
            final List<? extends GroupElement<?>> elements) {

        String uniqueIdsConcat = concatValues(elements);
        _md.update(uniqueIdsConcat.getBytes(_charsetName.getCharset()));
    }

    private String concatValues(
            final List<? extends GroupElement<?>> elements) {
        StringBuilder result = new StringBuilder();
        for (GroupElement<?> element : elements) {
            if (!(element instanceof ZpGroupElement)) {
                _md.reset();
                throw new CryptoLibException(
                    "Unsupported Group Elements type");
            }
            result.append(element.getValue());
        }
        return result.toString();
    }

    private void updateMessageDigestWith(final String data) {
        _md.update(data.getBytes(_charsetName.getCharset()));
    }

    /**
     * Converts an int to a byte array (containing 4 bytes).
     * 
     * @param intValue
     *            the int value to be converted.
     * @return the generated byte array.
     */
    public byte[] intToBytes(final int intValue) {
        return new byte[] {(byte) (intValue >> 24),
                (byte) (intValue >> 16), (byte) (intValue >> 8),
                (byte) intValue };
    }
}
