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

import java.math.BigInteger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.inject.Inject;
import javax.json.JsonArray;
import javax.json.JsonObject;

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

import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.mathematical.groups.impl.ZpGroupElement;
import com.scytl.cryptolib.mathematical.groups.impl.ZpSubgroup;
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.domain.model.rule.AbstractRule;
import com.scytl.products.ov.commons.util.JsonUtils;
import com.scytl.products.ov.commons.validation.ValidationError;
import com.scytl.products.ov.ei.services.domain.model.ballot.Ballot;
import com.scytl.products.ov.ei.services.domain.model.ballot.BallotRepository;
import com.scytl.products.ov.ei.services.domain.model.ballotbox.BallotBoxInformation;
import com.scytl.products.ov.ei.services.domain.model.ballotbox.BallotBoxInformationRepository;

public class WriteinsRule implements AbstractRule<Vote> {

    private final static String SERIALIZED_ENCRYPTED_WRITEINS_SEPARATOR = ";";
	
	private static final Pattern WRITE_IN_FORMAT = 
			Pattern.compile("^\\d*(" + SERIALIZED_ENCRYPTED_WRITEINS_SEPARATOR + "\\d*)*$");

    @Inject
    private BallotRepository ballotRepository;
    
    @Inject
    private BallotBoxInformationRepository ballotBoxInformationRepository;

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

    private static final String CONTESTS_FIELD = "contests";

    private static final String QUESTIONS_FIELD = "questions";

    private static final String WRITEINS_FIELD = "writeIn";

    private static final String MAX_CHOICES_FIELD = "max";
    
    private static final String ENCRYPTION_PARAMETERS = "encryptionParameters";

    private static final String ENCRYPTION_PARAMETER_P = "p";

    private static final String ENCRYPTION_PARAMETER_Q = "q";

    private static final String ENCRYPTION_PARAMETER_G = "g";

	private static final String WRITEINS_LENGTH_VALIDATION_ERROR_MSG = "The encrypted writeins didnt contain the expected number of elements";

	private static final String WRITEINS_REGEXP_VALIDATION_ERROR_MSG = "The encrypted writeins didnt contain correctly encoded values";

	private static final String WRITEINS_GROUP_ELEMENT_VALIDATION_ERROR_MSG = "The encrypted writeins do not belong to the correct mathematical group";

	private static final String WRITEINS_LENGTH_VALIDATION_EXCEPTION_WHILE_VALIDATING_MSG = "Error while trying to validate the encrypted writeins length";

    @Override
    public ValidationError execute(Vote vote) {
        ValidationError result = new ValidationError();

        try {

            BigInteger p, q, g;
            BallotBoxInformation ballotBoxInformation = ballotBoxInformationRepository
                    .findByTenantIdElectionEventIdBallotBoxId(vote.getTenantId(), 
                    		vote.getElectionEventId(), vote.getBallotBoxId());
            JsonObject json = JsonUtils.getJsonObject(ballotBoxInformation.getJson());
            JsonObject encryptionParameters = json.getJsonObject(ENCRYPTION_PARAMETERS);
            p = new BigInteger(encryptionParameters.getString(ENCRYPTION_PARAMETER_P));
            q = new BigInteger(encryptionParameters.getString(ENCRYPTION_PARAMETER_Q));
            g = new BigInteger(encryptionParameters.getString(ENCRYPTION_PARAMETER_G));

            final Ballot ballot = ballotRepository.findByTenantIdElectionEventIdBallotId(vote.getTenantId(),
                vote.getElectionEventId(), vote.getBallotId());

            final JsonObject jsonObject = JsonUtils.getJsonObject(ballot.getJson());
            JsonArray contestsArray = jsonObject.getJsonArray(CONTESTS_FIELD);

            int expectedNumWriteIns = 0;
            for (int j = 0; j < contestsArray.size(); j++) {
                JsonObject contest = contestsArray.getJsonObject(j);
                expectedNumWriteIns += getSumOfAllMaxValuesInThisContest(contest);
            }

            String serializedWriteins = vote.getEncryptedWriteIns();
            validateWriteInContent(result, expectedNumWriteIns, serializedWriteins, p, q, g);
        } catch (ResourceNotFoundException e) {
            LOG.error(WRITEINS_LENGTH_VALIDATION_EXCEPTION_WHILE_VALIDATING_MSG, e);
            result.setErrorArgs(new String[] {WRITEINS_LENGTH_VALIDATION_EXCEPTION_WHILE_VALIDATING_MSG });
        }

        return result;
    }

	private void validateWriteInContent(ValidationError result, int expectedNumWriteIns, 
			String serializedWriteins, BigInteger p, BigInteger q, BigInteger g) {
		int numWriteinElements;

		if ((serializedWriteins == null) || (serializedWriteins.length() == 0)) {
		    numWriteinElements = 0;
		} else {
		    numWriteinElements = serializedWriteins.split(SERIALIZED_ENCRYPTED_WRITEINS_SEPARATOR).length;
		}

		if (numWriteinElements != expectedNumWriteIns) {
		    result.setErrorArgs(new String[] {WRITEINS_LENGTH_VALIDATION_ERROR_MSG });
		    return;
		}

		if (numWriteinElements == 0) {
		    result.setValidationErrorType(ValidationErrorType.SUCCESS);
		    return;
		}
		Matcher matcher = WRITE_IN_FORMAT.matcher(serializedWriteins);
		if (matcher.matches()){
			String[] individualWriteIns = serializedWriteins.split(SERIALIZED_ENCRYPTED_WRITEINS_SEPARATOR);
			try {
				ZpSubgroup subGroup = new ZpSubgroup(g, p, q);
				for (String string : individualWriteIns) {
					ZpGroupElement element = new ZpGroupElement(new BigInteger(string), subGroup);
					if (!subGroup.isGroupMember(element)){
					    result.setErrorArgs(new String[] {WRITEINS_GROUP_ELEMENT_VALIDATION_ERROR_MSG });
					}
				}
			} catch (GeneralCryptoLibException e) {
			    result.setErrorArgs(new String[] {WRITEINS_GROUP_ELEMENT_VALIDATION_ERROR_MSG });
			    LOG.trace(e.getMessage(), e);
			}
		} else {
		    result.setErrorArgs(new String[] {WRITEINS_REGEXP_VALIDATION_ERROR_MSG });
		}

		if (result.getErrorArgs() == null) {
		    result.setValidationErrorType(ValidationErrorType.SUCCESS);
		}
	}

    private int getSumOfAllMaxValuesInThisContest(JsonObject contest) {

        JsonArray questionsArray = contest.getJsonArray(QUESTIONS_FIELD);
        Integer runningTotal = Integer.valueOf(0);

        for (int i = 0; i < questionsArray.size(); i++) {
            JsonObject question = questionsArray.getJsonObject(i);
            if (Boolean.valueOf(question.getString(WRITEINS_FIELD))) {
                Integer localMax = Integer.valueOf(question.getString(MAX_CHOICES_FIELD));
                runningTotal += localMax;
            }
        }

        return runningTotal;
    }

    @Override
    public String getName() {
        return RuleNames.VOTE_WRITEINS_VALIDATION.getText();
    }

}
