/*
 * 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.io.IOException;
import java.util.List;
import java.util.Map;

import javax.inject.Inject;

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

import com.fasterxml.jackson.databind.ObjectMapper;
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.ui.ws.rs.ObjectMappers;
import com.scytl.products.ov.commons.validation.ValidationError;
import com.scytl.products.ov.ei.services.domain.model.ballotbox.BallotBox;
import com.scytl.products.ov.ei.services.domain.model.ballotbox.BallotBoxRepository;
import com.scytl.products.ov.ei.services.domain.model.content.DynamicElectionInformationContentFactory;
import com.scytl.products.ov.ei.services.domain.model.content.ElectionInformationContent;
import com.scytl.products.ov.ei.services.domain.model.content.ElectionInformationContentDynamic;
import com.scytl.products.ov.ei.services.domain.model.content.ElectionInformationContentRepository;

/**
 * Validates number of allowed votes for voting card id and authentication
 * token.
 */
public class MaxNumberOfAllowedVotes implements AbstractRule<Vote> {

    @Inject
    private ElectionInformationContentRepository electionInformationContentRepository;

    @Inject
    private BallotBoxRepository ballotBoxRepository;

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

    @Inject
    private DynamicElectionInformationContentFactory dynamicElectionInformationContentFactory;

    /**
     * @see com.scytl.products.ov.commons.domain.model.rule.AbstractRule#execute(java.lang.Object)
     */
    @Override
    public ValidationError execute(Vote vote) {
        // validation result. By default is set to false
        ValidationError result = new ValidationError();
        result.setValidationErrorType(ValidationErrorType.FAILED);

        // recover election information content data
        ElectionInformationContent electionInformationContent = null;
        try {
            electionInformationContent =
                electionInformationContentRepository.findByTenantIdElectionEventId(vote.getTenantId(),
                    vote.getElectionEventId());
        } catch (ResourceNotFoundException e) {
            LOG.error("Error recovering data from election information content", e);
            return result;
        }

        ElectionInformationContentDynamic dynamicElectionInformationContent =
            dynamicElectionInformationContentFactory.aNew(electionInformationContent.getJson());
        int numVotesPerVotingCard = dynamicElectionInformationContent.numVotesPerVotingCard();
        int numVotesPerAuthToken = dynamicElectionInformationContent.numVotesPerAuthToken();

        // recover all votes for voting card
        List<BallotBox> storedVotesInBallotBox =
            ballotBoxRepository.findByTenantIdElectionEventIdVotingCardIdBallotBoxIdBallotId(vote.getTenantId(),
                vote.getElectionEventId(), vote.getVotingCardId(), vote.getBallotBoxId(), vote.getBallotId());

        // perform validations
        if (validateMaxNumVotesPerVotingCardId(storedVotesInBallotBox, numVotesPerVotingCard)
            && validateMaxNumVotesPerAuthToken(vote, storedVotesInBallotBox, numVotesPerAuthToken)) {
            result.setValidationErrorType(ValidationErrorType.SUCCESS);
        }

        return result;
    }

    private boolean validateMaxNumVotesPerVotingCardId(List<BallotBox> storedVotesInBallotBox,
            int maxNumVotesPerVotingCard) {
        // count all votes per voting card from bb
        return storedVotesInBallotBox.size() < maxNumVotesPerVotingCard;
    }

    private boolean validateMaxNumVotesPerAuthToken(Vote vote, List<BallotBox> storedVotesInBallotBox,
            int maxNumVotesPerAuthToken) {
        // count the occurrences of an auth token for each vote
        int numVotesForGivenAuthToken = 0;
        for (BallotBox ballotBox : storedVotesInBallotBox) {
            try {
                // check if the auth token is in any of the stored votes
                Vote storedVote = ObjectMappers.fromJson(ballotBox.getVote(), Vote.class);
                // compare both auth tokens
                boolean result =
                    compareAuthToken(vote.getAuthenticationToken(),
                        storedVote.getAuthenticationToken().replaceAll("\\\\", ""));
                if (result) {
                    numVotesForGivenAuthToken = numVotesForGivenAuthToken + 1;
                }
            } catch (IOException e) {
                LOG.error("Vote in json format can not be mapped to an object", e);
                return false;
            }
        }

        return numVotesForGivenAuthToken < maxNumVotesPerAuthToken;
    }

    @SuppressWarnings("unchecked")
    private boolean compareAuthToken(String authToken1, String authToken2) {
        try {
            ObjectMapper mapper = new ObjectMapper();
            Map<String, Object> a1 = mapper.readValue(authToken1, Map.class);
            Map<String, Object> a2 = mapper.readValue(authToken2, Map.class);
            return a1.equals(a2);
        } catch (IOException e) {
            LOG.error("Auth token in json format can not be mapped to be compared", e);
            return false;
        }
    }

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

}
