/*
 * 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.elgamal.factory;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.commons.utils.validations.Validate;
import com.scytl.cryptolib.elgamal.bean.ElGamalComputationsValues;
import com.scytl.cryptolib.elgamal.bean.ElGamalPrivateKey;
import com.scytl.cryptolib.elgamal.cryptoapi.CryptoAPIElGamalDecrypter;
import com.scytl.cryptolib.mathematical.groups.activity.ExponentCompressor;
import com.scytl.cryptolib.mathematical.groups.impl.Exponent;
import com.scytl.cryptolib.mathematical.groups.impl.ZpGroupElement;
import com.scytl.cryptolib.mathematical.groups.impl.ZpSubgroup;

/**
 * Implementation of an ElGamal decrypter.
 */
public final class CryptoElGamalDecrypter implements
        CryptoAPIElGamalDecrypter {

    private final ElGamalPrivateKey _elGamalPrivateKey;

    private final int _elGamalPrivateKeyLength;

    /**
     * Creates an ElGamal decrypter.
     * 
     * @param elGamalPrivateKey
     *            the ElGamal private key.
     */
    CryptoElGamalDecrypter(final ElGamalPrivateKey elGamalPrivateKey) {

        super();

        _elGamalPrivateKey = elGamalPrivateKey;
        _elGamalPrivateKeyLength = _elGamalPrivateKey.getKeys().size();
    }

    @Override
    public List<ZpGroupElement> decrypt(
            final ElGamalComputationsValues ciphertext,
            final boolean validateGroupMembership)
            throws GeneralCryptoLibException {

        Validate.notNull(ciphertext, "ElGamal ciphertext");
        Validate.notGreaterThan(ciphertext.getPhis().size(),
            _elGamalPrivateKeyLength, "ElGamal ciphertext length",
            "decrypter private key length");
        if (validateGroupMembership) {
            validateGroupMembership(ciphertext);
        }

        ElGamalPrivateKey keysAfterCompression =
            compressKeysIfNecessary(_elGamalPrivateKey, ciphertext
                .getPhis().size());

        List<ZpGroupElement> plaintext =
            new ArrayList<ZpGroupElement>(keysAfterCompression.getKeys()
                .size());

        // Perform decryption
        Exponent negatedExponent;
        for (int i = 0; i < ciphertext.getPhis().size(); i++) {

            // Compute the e = negate (-) of privKey[i]
            negatedExponent =
                keysAfterCompression.getKeys().get(i).negate();

            // Compute dm[i]= gamma^(e) * phi[i]
            plaintext.add(ciphertext.getGamma()
                .exponentiate(negatedExponent)
                .multiply(ciphertext.getPhis().get(i)));
        }

        return plaintext;
    }

    private ElGamalPrivateKey compressKeysIfNecessary(
            final ElGamalPrivateKey elGamalPrivateKey,
            final int numMessages) throws GeneralCryptoLibException {

        List<Exponent> keys = elGamalPrivateKey.getKeys();

        if (keys.size() <= numMessages) {
            return elGamalPrivateKey;
        }

        ExponentCompressor<ZpSubgroup> compressor =
            new ExponentCompressor<ZpSubgroup>(
                _elGamalPrivateKey.getGroup());

        List<Exponent> listWithCompressedFinalElement =
            compressor.buildListWithCompressedFinalElement(numMessages,
                keys);

        return new ElGamalPrivateKey(listWithCompressedFinalElement,
            _elGamalPrivateKey.getGroup());
    }

    private void validateGroupMembership(
            final ElGamalComputationsValues ciphertext)
            throws GeneralCryptoLibException {

        boolean hasOnlyGroupMembers = true;

        Iterator<ZpGroupElement> iterator =
            ciphertext.getValues().iterator();
        while (hasOnlyGroupMembers && iterator.hasNext()) {
            ZpGroupElement next = iterator.next();
            if (!_elGamalPrivateKey.getGroup().isGroupMember(next)) {
                hasOnlyGroupMembers = false;
            }
        }

        if (!hasOnlyGroupMembers) {
            throw new GeneralCryptoLibException(
                "ElGamal ciphertext contains one or more elements that do not belong to mathematical group of decrypter private key.");
        }
    }
}
