/*
 * 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.commons.dto;

import static com.scytl.products.ov.commons.safestream.StreamSerializableUtil.retrieveCertificateValueWithNullCheck;
import static com.scytl.products.ov.commons.safestream.StreamSerializableUtil.retrieveIntValueWithNullCheck;
import static com.scytl.products.ov.commons.safestream.StreamSerializableUtil.retrieveStringValueWithNullCheck;
import static com.scytl.products.ov.commons.safestream.StreamSerializableUtil.storeCertificateValueWithNullCheck;
import static com.scytl.products.ov.commons.safestream.StreamSerializableUtil.storeIntValueWithNullCheck;
import static com.scytl.products.ov.commons.safestream.StreamSerializableUtil.storeStringValueWithNullCheck;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.io.PrintWriter;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;

import org.msgpack.core.MessagePacker;
import org.msgpack.core.MessageUnpacker;

import com.scytl.products.ov.commons.beans.domain.model.messaging.StreamSerializableClassType;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.scytl.products.ov.commons.beans.domain.model.messaging.SafeStreamDeserializationException;

import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.products.ov.commons.beans.domain.model.messaging.CryptolibPayloadSignature;
import com.scytl.products.ov.commons.beans.domain.model.messaging.Payload;
import com.scytl.products.ov.commons.beans.domain.model.messaging.PayloadSignature;

public class ChoiceCodeGenerationReqPayload implements Payload {


    private static final Executor executor = Executors.newSingleThreadExecutor();

    private String tenantId;

    private String electionEventId;

    private String verificationCardSetId;

    private int chunkId;

    private List<ChoiceCodeGenerationInput> choiceCodeGenerationInputList;
    
    private PayloadSignature signature;
    
    public ChoiceCodeGenerationReqPayload() {
        super();
    }
    

    public ChoiceCodeGenerationReqPayload(String tenantId,
            String electionEventId,
            String verificationCardSetId,
            int chunkId) {
        this(tenantId, electionEventId, verificationCardSetId, chunkId, new ArrayList<>());
    }

    @JsonCreator
    public ChoiceCodeGenerationReqPayload(@JsonProperty("tenantId") String tenantId,
            @JsonProperty("electionEventId") String electionEventId,
            @JsonProperty("verificationCardSetId") String verificationCardSetId,
            @JsonProperty("chunkId") int chunkId,
            @JsonProperty("choiceCodeGenerationInputList") List<ChoiceCodeGenerationInput> choiceCodeGenerationInputList) {
        this.tenantId = tenantId;
        this.electionEventId = electionEventId;
        this.verificationCardSetId = verificationCardSetId;
        this.chunkId = chunkId;
        this.choiceCodeGenerationInputList = choiceCodeGenerationInputList;
    }

    public String getTenantId() {
        return tenantId;
    }

    public void setTenantId(String tenantId) {
        this.tenantId = tenantId;
    }

    public String getElectionEventId() {
        return electionEventId;
    }

    public void setElectionEventId(String electionEventId) {
        this.electionEventId = electionEventId;
    }

    public String getVerificationCardSetId() {
        return verificationCardSetId;
    }

    public void setVerificationCardSetId(String verificationCardSetId) {
        this.verificationCardSetId = verificationCardSetId;
    }

    public int getChunkId() {
        return chunkId;
    }

    public void setChunkId(int chunkId) {
        this.chunkId = chunkId;
    }

    public List<ChoiceCodeGenerationInput> getChoiceCodeGenerationInputList() {
        return choiceCodeGenerationInputList;
    }

    public void setChoiceCodeGenerationInputList(List<ChoiceCodeGenerationInput> choiceCodeGenerationInputList) {
        this.choiceCodeGenerationInputList = choiceCodeGenerationInputList;
    }

    @Override
    public void serialize(MessagePacker packer) throws IOException {
        storeStringValueWithNullCheck(packer, tenantId);
        storeStringValueWithNullCheck(packer, electionEventId);
        storeStringValueWithNullCheck(packer, verificationCardSetId);
        storeIntValueWithNullCheck(packer, chunkId);
        if (choiceCodeGenerationInputList == null) {
            packer.packNil();
        } else {
            packer.packArrayHeader(choiceCodeGenerationInputList.size());
            for (ChoiceCodeGenerationInput input : choiceCodeGenerationInputList) {
                storeStringValueWithNullCheck(packer, input.getEncryptedBallotCastingKey());
                storeStringValueWithNullCheck(packer, input.getEncryptedHashedBallotCastingKey());
                storeStringValueWithNullCheck(packer, input.getEncryptedRepresentations());
                storeStringValueWithNullCheck(packer, input.getVerificationCardId());
            }
        }
        if (signature == null) {
            packer.packNil();
        } else {
            packer.packArrayHeader(signature.getCertificateChain().length);
            for (X509Certificate cert : signature.getCertificateChain()) {
                try {
                    storeCertificateValueWithNullCheck(packer, cert);
                } catch (GeneralCryptoLibException e) {
                    throw new IOException(e);
                }
            }
            packer.packBinaryHeader(signature.getSignatureContents().length);
            packer.addPayload(signature.getSignatureContents());
        }
    }

    @Override
    public void deserialize(MessageUnpacker unpacker) throws SafeStreamDeserializationException {
        try {
            this.tenantId = retrieveStringValueWithNullCheck(unpacker);
            this.electionEventId = retrieveStringValueWithNullCheck(unpacker);
            this.verificationCardSetId = retrieveStringValueWithNullCheck(unpacker);
            this.chunkId = retrieveIntValueWithNullCheck(unpacker);
            if (unpacker.tryUnpackNil()) {
                this.choiceCodeGenerationInputList = new ArrayList<>();
            } else {
                int listSize = unpacker.unpackArrayHeader();
                choiceCodeGenerationInputList = new ArrayList<>(listSize);
                for (int i = 0; i < listSize; i++) {
                    String encryptedBallotCastingKey = retrieveStringValueWithNullCheck(unpacker);
                    String encryptedHashedBallotCastingKey = retrieveStringValueWithNullCheck(unpacker);
                    String encryptedRepresentations = retrieveStringValueWithNullCheck(unpacker);
                    String verificationCardId = retrieveStringValueWithNullCheck(unpacker);
                    ChoiceCodeGenerationInput input = new ChoiceCodeGenerationInput(verificationCardId, encryptedBallotCastingKey, encryptedHashedBallotCastingKey, encryptedRepresentations);
                    choiceCodeGenerationInputList.add(input);
                }
            }
            if (unpacker.tryUnpackNil()) {
                this.signature = null;
            } else {
                int arraySize = unpacker.unpackArrayHeader();
                X509Certificate[] certs = new X509Certificate[arraySize];
                for (int i = 0; i < arraySize; i++) {
                    try {
                        certs[i] = retrieveCertificateValueWithNullCheck(unpacker);
                    } catch (GeneralCryptoLibException e) {
                        throw new SafeStreamDeserializationException(e);
                    }
                }

                int signatureLength = unpacker.unpackBinaryHeader();
                byte[] signatureContents = unpacker.readPayload(signatureLength);

                this.signature = new CryptolibPayloadSignature(signatureContents, certs);
            }
        } catch (IOException e) {
            throw new SafeStreamDeserializationException(e);
        }
    }

    @Override
    public StreamSerializableClassType type() {
        return StreamSerializableClassType.CHOICE_CODE_GENERATION_REQ_PAYLOAD;
    }

    @Override
    public InputStream getSignableContent() throws IOException {
        // A pipe is created to be able to push the relevant objects in the
        // payload to an output stream. Another pipe is connected to the former,
        // and provides an input stream to read the aforementioned objects,
        // which will be the source of the signature.

        PipedInputStream pis = new PipedInputStream();
        PipedOutputStream pos = new PipedOutputStream(pis);

        // Feed the piped output stream from another thread to prevent
        // deadlocks.
        executor.execute(() -> streamSignableContent(pos));

        return pis;
    }

    /**
     * Streams the data to be signed into the provided output stream
     * 
     * @param os
     *            the output stream to push the data into
     */
    private void streamSignableContent(OutputStream os) {
        try (PrintWriter pw = new PrintWriter(os)) {
            pw.print(getTenantId());
            pw.print(getElectionEventId());
            pw.print(getVerificationCardSetId());
            getChoiceCodeGenerationInputList().forEach(input -> {
                pw.print(input.getEncryptedBallotCastingKey());
                pw.print(input.getEncryptedRepresentations());
                pw.print(input.getEncryptedHashedBallotCastingKey());
                pw.print(input.getVerificationCardId());
            });
            pw.flush();
        }
    }

    @JsonDeserialize(as = CryptolibPayloadSignature.class)
    @Override
    public PayloadSignature getSignature() {
        return signature;
    }

    @Override
    public void setSignature(PayloadSignature signature) {
        this.signature = signature;
    }

}
