/*
 * 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.choicecodes.domain.services;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;

import javax.inject.Inject;

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

import com.scytl.products.ov.channel.cc.orchestrator.commons.config.QueuesConfig;
import com.scytl.products.ov.channel.cc.orchestrator.commons.config.TopicsConfig;
import com.scytl.products.ov.channel.cc.orchestrator.commons.polling.PollingService;
import com.scytl.products.ov.commons.beans.exceptions.ResourceNotFoundException;
import com.scytl.products.ov.commons.dto.ChoiceCodeVerificationDTO;
import com.scytl.products.ov.commons.dto.ChoiceCodesVerificationDecryptResPayload;
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 ChoiceCodesDecryptionContributionsServiceImpl implements ChoiceCodesDecryptionContributionsService {
    
    @Inject
    private MessagingService messagingService;

    @Inject
    @ChoiceCodesDecryption
    @Contributions
    private MessageListener contributionsConsumer;
    
    @Inject
    @ChoiceCodesDecryption
    @ResultsReady
    private MessageListener resultsReadyConsumer;

    @Inject
    @ChoiceCodesDecryption
    private PollingService<List<byte[]>> pollingService;

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

    @Override
    public List<ChoiceCodesVerificationDecryptResPayload> requestChoiceCodesDecryptionContributionsSync(
            String trackingId, String tenantId, String electionEventId, String verificationCardSetId,
            String verificationCardId, String gammaStr) throws ResourceNotFoundException {

        LOG.info("OR - Requesting the choice codes decrypt contributions");

        ChoiceCodeVerificationDTO<String> requestDTO =
            publishGammasToReqQ(electionEventId, verificationCardSetId, verificationCardId, trackingId, gammaStr);

        return collectDecryptionContributionsResponses(requestDTO);
    }

    @Override
    public void startup() throws MessagingException {
        LOG.info("OR - Consuming the partial results ready notification topic");
        consumeResultsReady();
        LOG.info("OR - Consuming the choice codes decryption contributions queues");
        consumeComputeContributions();
    }
    
    @Override
    public void shutdown() throws MessagingException {
        LOG.info("OR - Disconnecting from the partial results ready notification topic");
        stopConsumingResultsReady();
        LOG.info("OR - Disconnecting from the choice codes decryption contributions queues");
        stopConsumingComputeContributions();
        
    }

    private void consumeComputeContributions() throws MessagingException {
        
        Queue[] queues = QueuesConfig.VERIFICATION_DECRYPTION_CONTRIBUTIONS_RES_QUEUES;
        
        if (queues == null || queues.length == 0) {
            throw new IllegalStateException(
                    "No choice codes decryption contributions response queues provided to listen to.");
        }
        
        for (Queue queue : queues) {
            messagingService.createReceiver(queue, contributionsConsumer);
        }
    }
    
    private void consumeResultsReady() throws MessagingException {
        messagingService.createReceiver(TopicsConfig.HA_TOPIC, resultsReadyConsumer);
    }

    private void stopConsumingComputeContributions() throws MessagingException {
        Queue[] queues = QueuesConfig.VERIFICATION_DECRYPTION_CONTRIBUTIONS_RES_QUEUES;
        
        if (queues == null || queues.length == 0) {
            throw new IllegalStateException(
                    "No choice codes decryption contributions response queues provided to listen to.");
        }
        
        for (Queue queue : queues) {
            messagingService.destroyReceiver(queue, contributionsConsumer);
        }
    }
    
    private void stopConsumingResultsReady() throws MessagingException {
        messagingService.destroyReceiver(TopicsConfig.HA_TOPIC, resultsReadyConsumer);
    }

    private ChoiceCodeVerificationDTO<String> publishGammasToReqQ(String electionEventId, String verificationCardSetId,
            String verificationCardId, String trackingId, String gammaStr)
            throws ResourceNotFoundException {

        Queue[] queues = QueuesConfig.VERIFICATION_DECRYPTION_CONTRIBUTIONS_REQ_QUEUES;

        if (queues == null || queues.length == 0) {
            throw new IllegalStateException(
                "No choice codes decryption contributions request queues provided to publish to.");
        }

        ChoiceCodeVerificationDTO<String> choiceCodeVerificationDTO = new ChoiceCodeVerificationDTO<>(UUID.randomUUID(), 
                trackingId, electionEventId, verificationCardSetId, verificationCardId, gammaStr);

        for (Queue queue : queues) {
            try {
                messagingService.send(queue, choiceCodeVerificationDTO);
            } catch (MessagingException e) {
                LOG.error("OR - Error publishing the decryption contributions to the nodes queues: " + e);
                throw new ResourceNotFoundException(
                    "Error publishing the decryption contributions to the nodes queues. ", e);
            }
        }
        return choiceCodeVerificationDTO;
    }

    private List<ChoiceCodesVerificationDecryptResPayload> collectDecryptionContributionsResponses(
            ChoiceCodeVerificationDTO<?> requestDTO) throws ResourceNotFoundException {

        UUID correlationId = requestDTO.getCorrelationId();

        List<byte[]> collectedContributions;
        try {

            LOG.info("OR - Waiting for the decryption contributions from the nodes to be returned.");

            collectedContributions = pollingService.getResults(correlationId);
        } catch (TimeoutException e) {
            LOG.error("OR - Error collecting the decryption contributions from the nodes: " + e);
            throw new ResourceNotFoundException("Error collecting the decryption contributions from the nodes. ", e);
        }

        return collectedContributions.stream()
            .map(ChoiceCodesDecryptionContributionsServiceImpl::deserializePayload)
            .collect(Collectors.toList());
    }
    
    private static ChoiceCodesVerificationDecryptResPayload deserializePayload(byte[] bytes) {
        try (ObjectInputStream stream = new ObjectInputStream(new ByteArrayInputStream(bytes))) {
            return (ChoiceCodesVerificationDecryptResPayload) stream.readObject();
        } catch (IOException | ClassNotFoundException e) {
            throw new IllegalStateException("Failed to deserialize the payload.", e);
        }
    }
}
