/*
 * 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.ChoiceCodesVerificationResPayload;
import com.scytl.products.ov.commons.dto.PartialCodesInput;
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 ChoiceCodesVerificationContributionsService {

    @Inject
    private MessagingService messagingService;

    @Inject
    @ChoiceCodesVerification
    @Contributions
    private MessageListener contributionsConsumer;

    @Inject
    @ChoiceCodesVerification
    @ResultsReady
    private MessageListener resultsReadyConsumer;

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

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

    public List<ChoiceCodesVerificationResPayload> request(String trackingId, String tenantId, String electionEventId,
            String verificationCardSetId, String verificationCardId, PartialCodesInput partialCodes)
            throws IOException, ResourceNotFoundException {

        LOG.info("OR - Requesting the choice codes compute contributions for the voting phase");

        ChoiceCodeVerificationDTO<PartialCodesInput> requestDTO = publishPartialCodesToReqQ(electionEventId,
            verificationCardSetId, verificationCardId, trackingId, partialCodes);

        return collectPartialCodesContributionsResponses(requestDTO);
    }

    public void startup() throws MessagingException {
        LOG.info("OR - Consuming the partial results ready notification topic");
        consumeResultsReady();
        LOG.info("OR - Consuming the choice codes compute contributions queues");
        consumeComputeContributions();
    }

    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 compute contributions queues");
        stopConsumingComputeContributions();
    }

    private void consumeComputeContributions() throws MessagingException {

        Queue[] queues = QueuesConfig.VERIFICATION_COMPUTE_CONTRIBUTIONS_RES_QUEUES;

        if (queues.length == 0) {
            throw new IllegalStateException(
                "No choice codes compute 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_COMPUTE_CONTRIBUTIONS_RES_QUEUES;

        if (queues.length == 0) {
            throw new IllegalStateException(
                "No choice codes compute 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<PartialCodesInput> publishPartialCodesToReqQ(String electionEventId,
            String verificationCardSetId, String verificationCardId, String trackingId, PartialCodesInput partialCodes)
            throws ResourceNotFoundException {

        Queue[] queues = QueuesConfig.VERIFICATION_COMPUTE_CONTRIBUTIONS_REQ_QUEUES;

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

        UUID correlationId = UUID.randomUUID();

        ChoiceCodeVerificationDTO<PartialCodesInput> choiceCodeVerificationDTO = new ChoiceCodeVerificationDTO<>(
            correlationId, trackingId, electionEventId, verificationCardSetId, verificationCardId, partialCodes);

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

    private List<ChoiceCodesVerificationResPayload> collectPartialCodesContributionsResponses(
            ChoiceCodeVerificationDTO<?> requestDTO) throws ResourceNotFoundException {

        UUID correlationId = requestDTO.getCorrelationId();

        List<byte[]> collectedContributions;
        try {

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

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

        return collectedContributions.stream().map(ChoiceCodesVerificationContributionsService::deserializePayload)
            .collect(Collectors.toList());
    }

    private static ChoiceCodesVerificationResPayload deserializePayload(byte[] bytes) {
        try (ObjectInputStream stream = new ObjectInputStream(new ByteArrayInputStream(bytes))) {
            return (ChoiceCodesVerificationResPayload) stream.readObject();
        } catch (IOException | ClassNotFoundException e) {
            throw new IllegalStateException("Failed to deserialize the payload.", e);
        }
    }
}
