/*
 * 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.tallying.api;

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

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

import com.scytl.products.oscore.logging.api.domain.Level;
import com.scytl.products.oscore.logging.api.domain.LogContent;
import com.scytl.products.oscore.logging.api.writer.SecureLoggingWriter;
import com.scytl.products.ov.commons.beans.Contest;
import com.scytl.products.ov.tallying.api.dto.output.TallyingOutput;
import com.scytl.products.ov.tallying.command.TallyingHolder;
import com.scytl.products.ov.tallying.commons.beans.ContestVote;
import com.scytl.products.ov.tallying.commons.beans.ErrorCodes;
import com.scytl.products.ov.tallying.commons.beans.OptionData;
import com.scytl.products.ov.tallying.commons.beans.TallyingError;
import com.scytl.products.ov.tallying.commons.log.TallyingLogConstants;
import com.scytl.products.ov.tallying.commons.log.TallyingLogEvents;

public class TallyingVotesService {

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

    private final SecureLoggingWriter loggingWriter;

    public TallyingVotesService(SecureLoggingWriter loggingWriter) {
       this.loggingWriter = loggingWriter;
    }

    public void tally(final TallyingHolder holder, final TallyingOutput output) {

        Map<BigInteger, ContestVote> contestVotes = new HashMap<>();

        for (List<String> votingOptionRepresentationList : holder.getDecryptedValues()) {

            List<ContestVote> voteParsedByContests = parse(votingOptionRepresentationList, holder, output);

            for (ContestVote contestVote : voteParsedByContests) {
                addToTallyResult(contestVote, contestVotes);
            }
        }

        output.setContestVotes(new ArrayList<>(contestVotes.values()));
    }

    private void addToTallyResult(ContestVote contestVote, Map<BigInteger, ContestVote> contestVotes) {
        ContestVote existingContestVote = contestVotes.get(contestVote.getMultiplication());
        if (existingContestVote == null) {
            contestVotes.put(contestVote.getMultiplication(), contestVote);
        } else {
            existingContestVote.increment();
        }
    }

    private List<ContestVote> parse(final List<String> votingOptionRepresentationList, final TallyingHolder holder,
            final TallyingOutput output) {
        String separator = holder.getWriteInSeparator();
        List<String> cleanedVotingOptionRepresentationList =
            cleanDuplicatedWriteInRepresentations(votingOptionRepresentationList, separator);
        Map<String, ContestVote> voteParsedByContests = new HashMap<>();
        for (String representation : cleanedVotingOptionRepresentationList) {
            String cleanedRepresentation = representation;
            String writeInValue = null;
            if (representation.contains(separator)) {
                String[] split = representation.split(separator);
                cleanedRepresentation = split[0];
                writeInValue = representation.replaceFirst(split[0] + separator, "");
            }
            Contest contest = holder.getOptionContestMappings().get(cleanedRepresentation);
            if (contest != null) {
                tallyRepresentation(cleanedRepresentation, writeInValue, contest, voteParsedByContests);
            } else {
                logRepresentationError(holder, representation, output);
            }
        }
        return new ArrayList<>(voteParsedByContests.values());
    }

    private List<String> cleanDuplicatedWriteInRepresentations(List<String> votingOptionRepresentationList,
            String separator) {
        List<String> cleanedVotingOptionRepresentationList = new ArrayList<>(votingOptionRepresentationList);
        Set<String> writeinRepresentations = new HashSet<>();
        for (String representation : votingOptionRepresentationList) {
            if (representation.contains(separator)) {
                String[] split = representation.split(separator);
                writeinRepresentations.add(split[0]);
            }
        }
        cleanedVotingOptionRepresentationList.removeAll(writeinRepresentations);
        return cleanedVotingOptionRepresentationList;
    }

    private void tallyRepresentation(final String representation, String writeInValue, final Contest contest,
            final Map<String, ContestVote> voteParsedByContests) {

        ContestVote contestVote = voteParsedByContests.get(contest.getId());
        if (contestVote == null) {
            OptionData optionData = new OptionData(representation, writeInValue);
            contestVote = new ContestVote(contest.getId(), optionData);
            voteParsedByContests.put(contest.getId(), contestVote);
        } else {
            OptionData optionData = new OptionData(representation, writeInValue);
            contestVote.add(optionData);
        }
    }

    private void logRepresentationError(TallyingHolder holder, final String representation,
            final TallyingOutput output) {
        String errorMessage = "The decrypted ballot box has been tampered with and contains the representation \""
            + representation + "\", which is not present in the ballot";
        LOG.error(errorMessage);

        loggingWriter.log(Level.ERROR,
            new LogContent.LogContentBuilder().logEvent(TallyingLogEvents.TALLYING_PROCESS_FAILURE)
                .objectId(holder.getBallotBoxId()).user("adminID").electionEvent(holder.getElectionEventId())
                .additionalInfo(TallyingLogConstants.ERR_DESC, errorMessage).createLogInfo());

        output.addError(new TallyingError(errorMessage, ErrorCodes.UNKNOWN_ERROR));
    }
}
