/*
 * 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.channel.cc.orchestrator.mixdec.domain.services;

import java.util.concurrent.TimeoutException;

import javax.inject.Inject;

import org.slf4j.Logger;

import com.scytl.products.ov.channel.cc.orchestrator.mixdec.infrastructure.persistence.MixDecNodeOutputRepository;
import com.scytl.products.ov.commons.beans.domain.model.ballotbox.BallotBoxId;
import com.scytl.products.ov.commons.beans.domain.model.vote.VoteSetId;
import com.scytl.products.ov.commons.beans.exceptions.ApplicationException;
import com.scytl.products.ov.commons.beans.exceptions.DuplicateEntryException;
import com.scytl.products.ov.commons.dto.MixingDTO;
import com.scytl.products.ov.commons.dto.MixingError;
import com.scytl.products.ov.commons.dto.mixing.MixingPayload;
import com.scytl.products.ov.commons.messaging.MessageListener;
import com.scytl.products.ov.commons.messaging.MessagingException;
import com.scytl.products.ov.commons.messaging.MessagingService;
import com.scytl.products.ov.commons.messaging.Queue;

public class MixDecMixingDecryptionConsumer implements MessageListener {
    @Inject
    private Logger LOG;

    @Inject
    private MixDecNodeOutputRepository mixDecNodeOutputRepository;

    @Inject
    private MixDecBallotBoxService mixDecBallotBoxService;

    @Inject
    private MessagingService messagingService;

    /**
     * Receives the result of mixing and decrypting in one node. If the DTO has
     * been through all the nodes already, the process is finished. Otherwise,
     * the resulting DTO must be sent to one of the nodes that have not yet
     * visited.
     *
     * @param message
     *            the output mixing DTO that has been processed by a node.
     */
    @Override
    public void onMessage(Object message) {
        MixingDTO receivedDTO = (MixingDTO) message;
        VoteSetId voteSetId = receivedDTO.getPayload().getVoteSetId();

        LOG.info("Received vote set {} from node {}", voteSetId, receivedDTO.getLastNodeName());

        MixingError mixingError = receivedDTO.getError();

        // Check whether the DTO is reporting an error.
        if (null == mixingError) {
            MixingPayload payload = receivedDTO.getPayload();
            LOG.debug("Processing vote set {} in request {}...", payload.getVoteSetId(), receivedDTO.getRequestId());

            // Persist the received DTO
            persistPartialResults(receivedDTO);

            // Check the visited nodes list.
            if (receivedDTO.getQueuesToVisit().isEmpty()) {
                // All nodes have been visited. Store the final results.
                persistFinalResults(receivedDTO.getPayload());
            } else {
                // If the DTO has not yet been through all nodes, as shown by an
                // empty queuesToVisit property, send it to one of the unvisited
                // nodes.
                try {
                    mixDecBallotBoxService.process(receivedDTO);
                } catch (TimeoutException toE) {
                    LOG.error("Mixing payload {} delivery timed out", payload.getVoteSetId(), toE);
                } catch (ApplicationException aE) {
                    LOG.error("Error mixing payload {}.", payload.getVoteSetId(), aE);
                }

            }
        } else {
            Queue queue = new Queue(receivedDTO.getLastQueueName());
            LOG.warn("Payload {} processing failed", voteSetId, receivedDTO.getError());

            BallotBoxId bbid = voteSetId.getBallotBoxId();
            mixDecBallotBoxService.addMixingErrorStatus(bbid.getTenantId(), bbid.getElectionEventId(), bbid.getId(),
                mixingError.getErrorMessage());
            try {
                if (receivedDTO.getError().getRetriesLeft() > 0) {
                    messagingService.send(queue, receivedDTO);
                } else {
                    // No retries left.
                    LOG.error("Processing payload {} will not be further attempted", voteSetId, receivedDTO.getError());
                }
            } catch (MessagingException e) {
                LOG.error("Payload {} could not be processed", e);
            }
        }
    }

    /**
     * Stores the results of having mixed and decrypted a ballot box in one
     * node.
     *
     * @param mixingDTO
     *            the data as coming out from an online mixing node.
     */
    private void persistPartialResults(MixingDTO mixingDTO) {
        LOG.debug("Storing mixing and decryption results for vote set {} from node {}...",
            mixingDTO.getPayload().getVoteSetId(), mixingDTO.getLastNodeName());

        try {
            mixDecNodeOutputRepository.save(mixingDTO);
            LOG.info("Mixing and decryption results for vote set {} from node {} have been stored",
                mixingDTO.getPayload().getVoteSetId(), mixingDTO.getLastNodeName());
        } catch (DuplicateEntryException e) {
            LOG.warn("Node {} output for vote set {} is already stored", mixingDTO.getLastNodeName(),
                mixingDTO.getPayload().getVoteSetId());
        }
    }

    /**
     * Stores the results of having fully mixed and decrypted a vote set.
     *
     * @param payload
     *            the data as coming out from the last online mixing node.
     */
    private void persistFinalResults(MixingPayload payload) {
        LOG.debug("Storing final mixing and decryption results for vote set {}...", payload.getVoteSetId());

        // Update the ballot box status accordingly.
        mixDecBallotBoxService.addProcessedBallotBoxWithStatus(payload);
        LOG.info("Vote set {} has been fully processed.", payload.getVoteSetId());
    }
}
