/*
 * 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.infrastructure.persistence;

import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.sql.Clob;
import java.sql.SQLException;

import javax.ejb.Stateless;
import javax.persistence.NoResultException;
import javax.persistence.Query;
import javax.persistence.TypedQuery;

import org.hibernate.SQLQuery;
import org.hibernate.Session;

import com.scytl.products.ov.channel.cc.orchestrator.choicecodes.domain.model.computedvalues.ComputedValues;
import com.scytl.products.ov.channel.cc.orchestrator.choicecodes.domain.model.computedvalues.ComputedValuesRepository;
import com.scytl.products.ov.commons.beans.exceptions.ResourceNotFoundException;
import com.scytl.products.ov.commons.infrastructure.persistence.BaseRepositoryImpl;

/**
 * The implementation of the operations on the computed values repository. The
 * implementation uses JPA as data layer to access database.
 */
@Stateless
public class ComputedValuesRepositoryImpl extends BaseRepositoryImpl<ComputedValues, Integer>
        implements ComputedValuesRepository {

    /* The name of parameter tenant id */
    private static final String PARAMETER_TENANT_ID = "tenantId";

    /* The name of parameter election event id */
    private static final String PARAMETER_ELECTION_EVENT_ID = "electionEventId";

    /* The name of parameter verification card set id */
    private static final String PARAMETER_VERIFICATION_CARD_SET_ID = "verificationCardSetId";
    
    /* The name of parameter chunk id */
    private static final String PARAMETER_CHUNK_ID = "chunkId";

    private static final int BUFFER_SIZE = 8192;

    @Override
    public ComputedValues findByTenantIdElectionEventIdVerificationCardSetId(String tenantId, String electionEventId,
            String verificationCardSetId, int chunkId) throws ResourceNotFoundException {
        TypedQuery<ComputedValues> query = entityManager.createQuery(
            "SELECT c FROM ComputedValues c WHERE c.tenantId = :tenantId AND c.electionEventId = :electionEventId "
            + "AND c.verificationCardSetId = :verificationCardSetId AND c.chunkId = :chunkId",
            ComputedValues.class);
        query.setParameter(PARAMETER_TENANT_ID, tenantId);
        query.setParameter(PARAMETER_ELECTION_EVENT_ID, electionEventId);
        query.setParameter(PARAMETER_VERIFICATION_CARD_SET_ID, verificationCardSetId);
        query.setParameter(PARAMETER_CHUNK_ID, chunkId);

        try {
            return query.getSingleResult();
        } catch (NoResultException e) {
            throw new ResourceNotFoundException("", e);
        }
    }

    @Override
    public void writeJsonToStreamForTenantIdElectionEventIdVerificationCardSetIdChunkId(OutputStream stream, String tenantId,
            String electionEventId, String verificationCardSetId, int chunkId) throws ResourceNotFoundException, IOException {
        String computedValuesJsonQuery =
            "SELECT c.json FROM computed_values c WHERE c.tenant_id = :tenantId AND c.election_event_id = :electionEventId "
            + "AND c.verification_card_set_id = :verificationCardSetId AND c.chunk_id = :chunkId";
        final Session session = entityManager.unwrap(Session.class);
        final SQLQuery query = session.createSQLQuery(computedValuesJsonQuery);

        query.setParameter(PARAMETER_TENANT_ID, tenantId);
        query.setParameter(PARAMETER_ELECTION_EVENT_ID, electionEventId);
        query.setParameter(PARAMETER_VERIFICATION_CARD_SET_ID, verificationCardSetId);
        query.setParameter(PARAMETER_CHUNK_ID, chunkId);

        try {
            Clob result = (Clob) query.uniqueResult();
            if (null == result) {
                throw new ResourceNotFoundException(
                    "Verification Card Set " + verificationCardSetId + " contributions generation is not completed");
            }
            final char[] buffer = new char[BUFFER_SIZE];
            try (Reader resultReader = result.getCharacterStream(1, (int) result.length());
                    Writer resultWriter = new OutputStreamWriter(stream, StandardCharsets.UTF_8)) {
                int read;
                while ((read = resultReader.read(buffer)) != -1) {
                    resultWriter.write(buffer, 0, read);
                }
            }
        } catch (NoResultException | SQLException e) {
            throw new ResourceNotFoundException("", e);
        }
    }

    @Override
    public boolean existsByTenantIdElectionEventIdVerificationCardSetId(String tenantId, String electionEventId,
            String verificationCardSetId) throws ResourceNotFoundException {
        Query query = entityManager.createQuery(
            "SELECT COUNT(1) FROM ComputedValues c WHERE c.tenantId = :tenantId AND c.electionEventId = :electionEventId "
            + "AND c.verificationCardSetId = :verificationCardSetId");
        query.setParameter(PARAMETER_TENANT_ID, tenantId);
        query.setParameter(PARAMETER_ELECTION_EVENT_ID, electionEventId);
        query.setParameter(PARAMETER_VERIFICATION_CARD_SET_ID, verificationCardSetId);
        try {
            return 0L < (Long) query.getSingleResult();
        } catch (NoResultException e) {
            throw new ResourceNotFoundException("", e);
        }
    }
    
    @Override
    public boolean existsByTenantIdElectionEventIdVerificationCardSetIdChunkId(String tenantId, String electionEventId,
            String verificationCardSetId, int chunkId) throws ResourceNotFoundException {
        Query query = entityManager.createQuery(
            "SELECT COUNT(1) FROM ComputedValues c WHERE c.tenantId = :tenantId AND c.electionEventId = :electionEventId "
            + "AND c.verificationCardSetId = :verificationCardSetId AND c.chunkId = :chunkId");
        query.setParameter(PARAMETER_TENANT_ID, tenantId);
        query.setParameter(PARAMETER_ELECTION_EVENT_ID, electionEventId);
        query.setParameter(PARAMETER_VERIFICATION_CARD_SET_ID, verificationCardSetId);
        query.setParameter(PARAMETER_CHUNK_ID, chunkId);
        try {
            return 0L < (Long) query.getSingleResult();
        } catch (NoResultException e) {
            throw new ResourceNotFoundException("", e);
        }
    }

    @Override
    public boolean isComputedByTenantIdElectionEventIdVerificationCardSetIdChunkId(String tenantId, String electionEventId,
            String verificationCardSetId, int chunkId) throws ResourceNotFoundException {
        Query query = entityManager.createQuery(
            "SELECT COUNT(1) FROM ComputedValues c WHERE c.tenantId = :tenantId AND c.electionEventId = :electionEventId "
            + "AND c.verificationCardSetId = :verificationCardSetId AND c.chunkId = :chunkId AND c.json IS NOT NULL");
        query.setParameter(PARAMETER_TENANT_ID, tenantId);
        query.setParameter(PARAMETER_ELECTION_EVENT_ID, electionEventId);
        query.setParameter(PARAMETER_VERIFICATION_CARD_SET_ID, verificationCardSetId);
        query.setParameter(PARAMETER_CHUNK_ID, chunkId);
        try {
            return 1 == ((Long) query.getSingleResult()).intValue();
        } catch (NoResultException e) {
            throw new ResourceNotFoundException("", e);
        }
    }

    @Override
    public boolean areComputedByTenantIdElectionEventIdVerificationCardSetId(
            String tenantId, String electionEventId,
            String verificationCardSetId, int chunkCount) {
        Query query = entityManager.createQuery(
            "SELECT COUNT(1) FROM ComputedValues c WHERE c.tenantId = :tenantId AND c.electionEventId = :electionEventId "
            + "AND c.verificationCardSetId = :verificationCardSetId AND c.json IS NOT NULL");
        query.setParameter(PARAMETER_TENANT_ID, tenantId);
        query.setParameter(PARAMETER_ELECTION_EVENT_ID, electionEventId);
        query.setParameter(PARAMETER_VERIFICATION_CARD_SET_ID, verificationCardSetId);
        return chunkCount == ((Long) query.getSingleResult()).intValue();
    }
}
