/*
 * 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.validation.decompress;

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

import java.math.BigInteger;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import com.scytl.products.ov.commons.beans.Ballot;
import com.scytl.products.ov.commons.beans.Contest;
import com.scytl.products.ov.commons.beans.ElectionOption;
import com.scytl.products.ov.commons.correctness.CachedOptionCorrectnessEvaluator;
import com.scytl.products.ov.commons.correctness.CorrectnessError;
import com.scytl.products.ov.commons.correctness.feedback.CorrectnessFeedback;
import com.scytl.products.ov.commons.correctness.feedback.ReportedError;
import com.scytl.products.ov.validation.commons.beans.BigIntegerFactor;
import com.scytl.products.ov.validation.commons.beans.Factorization;
import com.scytl.products.ov.validation.commons.beans.ValidationFailure;
import com.scytl.products.ov.validation.commons.beans.ValidationFailureCodes;

/**
 * Class that extracts correctness rule from ballot.json and validates factorizations against the rule.
 */
public class VoteValidator {

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

	private final CachedOptionCorrectnessEvaluator ce = new CachedOptionCorrectnessEvaluator(LOG);

	    /**
     * Adds to validationFailures all validation failures found in factorization.
     *
     * @param factorization
     *            the factorization of the vote.
     * @param validationFailures
     *            is used to store validation failures found during the validation.
     * @return true if the validation was passed, false otherwise
     */
    public boolean validate(Ballot ballot, final Factorization factorization, final List<ValidationFailure> validationFailures) {

		List<BigIntegerFactor> factors = factorization.getFactors();
		Set<String> representations = new HashSet<>(factors.size());
		for (BigIntegerFactor bigIntegerFactor : factors) {
			representations.add(bigIntegerFactor.getBase().toString());
		}
		List<Contest> contests = ballot.getContests();
		allowOnlyExistingOptions(validationFailures, representations, contests);
    	for (Contest contest : contests) {
    		List<String> filteredRepresentations = allowOnlyExistingOptionsforContest(representations, contest);
    		String decryptedCorrectnessRule = contest.getDecryptedCorrectnessRule();
        	CorrectnessFeedback evaluateCorrectness = ce.evaluateCorrectness(CorrectnessFeedback.decorateRule(decryptedCorrectnessRule), filteredRepresentations);
    		if(!evaluateCorrectness.getResult()){
    			List<ReportedError> errors = evaluateCorrectness.getErrors();
    			for (ReportedError reportedError : errors) {
                    ValidationFailure failure = new ValidationFailure("Rule validation error of type " + reportedError.getErrorType().toString() + " in " + reportedError.getReference(),
                            ValidationFailureCodes.RULE_VALIDATION);
                        validationFailures.add(failure);
				}
    		}
		}

        if (!BigInteger.ONE.equals(factorization.getRemainder())) {
            ValidationFailure failure = new ValidationFailure("There were some options left after factorizing.",
                ValidationFailureCodes.NON_FACTORIZABLE_REMAINDER);
            validationFailures.add(failure);
        }

        if (factorization.getFactors().stream().filter(factor -> factor.getExponent() > 1).count() > 0) {
            ValidationFailure failure = new ValidationFailure("There were duplicated values in the factorization.",
                ValidationFailureCodes.DUPLICATED_FACTOR);
            validationFailures.add(failure);
        }

        return validationFailures.isEmpty();
    }

	private void allowOnlyExistingOptions(final List<ValidationFailure> validationFailures, Set<String> representations,
			List<Contest> contests) {
		Set<String> validRepresentations = new HashSet<>();
		for (Contest contest : contests) {
			for (ElectionOption electionOption : contest.getOptions()) {
				validRepresentations.add(electionOption.getRepresentation());
			}
		}
		for (String string : representations) {
			if (!validRepresentations.contains(string)) {
                ValidationFailure failure = new ValidationFailure("Rule validation error of type " + CorrectnessError.SELECTION_ERROR.toString() + ": " + string,
                        ValidationFailureCodes.RULE_VALIDATION);
				validationFailures.add(failure);
			}
		}
	}
	private List<String> allowOnlyExistingOptionsforContest(Set<String> representations, Contest contest) {
		Set<String> validRepresentations = new HashSet<>();
		for (ElectionOption electionOption : contest.getOptions()) {
			validRepresentations.add(electionOption.getRepresentation());
		}
		List<String> validSelection=new ArrayList<>();
		for (String string : representations) {
			if (validRepresentations.contains(string)) {
				validSelection.add(string);
			}
		}
		return validSelection;
	}
}
