/*
 * 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.vv.domain.model.rule;

import java.io.IOException;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;

import javax.inject.Inject;

import org.apache.commons.codec.binary.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.api.proofs.ProofsServiceAPI;
import com.scytl.cryptolib.elgamal.bean.ElGamalPublicKey;
import com.scytl.cryptolib.mathematical.groups.impl.ZpGroupElement;
import com.scytl.cryptolib.mathematical.groups.impl.ZpSubgroup;
import com.scytl.cryptolib.proofs.proof.Proof;
import com.scytl.products.ov.commons.beans.EncryptionParameters;
import com.scytl.products.ov.commons.beans.VoteVerificationContextData;
import com.scytl.products.ov.commons.beans.domain.model.vote.Vote;
import com.scytl.products.ov.commons.beans.exceptions.ResourceNotFoundException;
import com.scytl.products.ov.commons.beans.validation.ValidationErrorType;
import com.scytl.products.ov.commons.crypto.Utils;
import com.scytl.products.ov.commons.domain.model.rule.AbstractRule;
import com.scytl.products.ov.commons.ui.ws.rs.ObjectMappers;
import com.scytl.products.ov.commons.validation.ValidationError;
import com.scytl.products.ov.vv.domain.model.content.VerificationContentEntity;
import com.scytl.products.ov.vv.domain.model.content.VerificationContentRepository;
import com.scytl.products.ov.vv.domain.model.verification.Verification;
import com.scytl.products.ov.vv.domain.model.verification.VerificationRepository;

/**
 * This is the implementation of a validator of the exponentiation proof.
 */
public class ExponentiationProofRule implements AbstractRule<Vote> {

    @Inject
    private VerificationRepository verificationRepository;

    @Inject
    private VerificationContentRepository verificationContentRepository;

    @Inject
    private ProofsServiceAPI proofsService;

    private static final Logger LOG = LoggerFactory.getLogger(ExponentiationProofRule.class);

    /**
     * This method validates from the given vote the Exponentiation proof. The
     * steps are: generating the base elements, exponentiated elements and
     * needed as input for the validator in the cryptolib.
     * 
     * @see com.scytl.products.ov.commons.domain.model.rule.AbstractRule#execute(java.lang.Object)
     */
    @Override
    public ValidationError execute(Vote vote) {
        ValidationError result = new ValidationError();

        VoteVerificationContextData voteVerificationContextData = null;
        try {

            Verification verification = verificationRepository.findByTenantIdElectionEventIdVerificationCardId(
                vote.getTenantId(), vote.getElectionEventId(), vote.getVerificationCardId());
            VerificationContentEntity verificationContentEntity =
                verificationContentRepository.findByTenantIdElectionEventIdVerificationCardSetId(vote.getTenantId(),
                    vote.getElectionEventId(), verification.getVerificationCardSetId());
            voteVerificationContextData =
                ObjectMappers.fromJson(verificationContentEntity.getJson(), VoteVerificationContextData.class);
        } catch (ResourceNotFoundException | IOException e) {
            LOG.error("", e);
            return result;
        }

        EncryptionParameters encryptionParameters = voteVerificationContextData.getEncryptionParameters();
        String generatorEncryptParam = encryptionParameters.getG();
        String pEncryptParam = encryptionParameters.getP();
        String qEncryptParam = encryptionParameters.getQ();

        ZpSubgroup mathematicalGroup;
        try {
            BigInteger generatorEncryptParamBigInteger = new BigInteger(generatorEncryptParam);
            BigInteger pEncryptParamBigInteger = new BigInteger(pEncryptParam);
            BigInteger qEncryptParamBigInteger = new BigInteger(qEncryptParam);
            mathematicalGroup =
                new ZpSubgroup(generatorEncryptParamBigInteger, pEncryptParamBigInteger, qEncryptParamBigInteger);

            // small validation-> check if ciphertext elements are from group
            // defined by g, p and q
            List<ZpGroupElement> groupElements = new ArrayList<>();
            String[] ciphertextElements = Utils.getCiphertextElementsFromEncryptedOptions(vote.getEncryptedOptions());
            for (String elementValue : ciphertextElements) {
                ZpGroupElement groupElement = new ZpGroupElement(new BigInteger(elementValue), mathematicalGroup);
                groupElements.add(groupElement);
            }

            // create the input parameter for the cryptolib validator of
            // exponentiation proof
            // create a list of exponentiated elements as group elements of
            // verification card public key and ciphertext
            // elements
            List<ZpGroupElement> exponentiatedElements = new ArrayList<>();
            String verificationCardPublicKeyString =
                new String(Base64.decodeBase64(vote.getVerificationCardPublicKey()), StandardCharsets.UTF_8);
            // it is assumed that there is only one exponent in the public key
            ZpGroupElement verificationCardPublicKey =
                ElGamalPublicKey.fromJson(verificationCardPublicKeyString).getKeys().get(0);
            exponentiatedElements.add(verificationCardPublicKey);
            ciphertextElements = Utils.getCiphertextElementsFromEncryptedOptions(vote.getCipherTextExponentiations());
            for (String elementValue : ciphertextElements) {
                ZpGroupElement exponentiatedElement =
                    new ZpGroupElement(new BigInteger(elementValue), mathematicalGroup);
                exponentiatedElements.add(exponentiatedElement);
            }

            // create a list of base elements for generator and ciphertext
            // elements
            List<ZpGroupElement> baseElements = new ArrayList<>();
            ZpGroupElement groupElementGenerator =
                new ZpGroupElement(generatorEncryptParamBigInteger, mathematicalGroup);
            baseElements.add(groupElementGenerator);
            for (ZpGroupElement groupElement : groupElements) {
                baseElements.add(groupElement);
            }

            if (proofsService.createProofVerifierAPI(mathematicalGroup).verifyExponentiationProof(exponentiatedElements,
                baseElements, Proof.fromJson(vote.getExponentiationProof()))) {
                result.setValidationErrorType(ValidationErrorType.SUCCESS);
            }
            // for now any exception that could occur will be just caught and
            // logged
            // making the result of the validation being a false
            // the number format exception is in case there is a problem with
            // the instantiation of a BigInteger for the encryption parameters
        } catch (GeneralCryptoLibException | NumberFormatException e) {
            LOG.error("", e);
        }
        return result;
    }

    /**
     * @see com.scytl.products.ov.commons.domain.model.rule.AbstractRule#getName()
     */
    @Override
    public String getName() {
        return RuleNames.VOTE_EXPONENTIATION_PROOF.getText();
    }
}
