/*
 * 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.retrieveStringValueWithNullCheck;
import static com.scytl.products.ov.commons.safestream.StreamSerializableUtil.storeCertificateValueWithNullCheck;
import static com.scytl.products.ov.commons.safestream.StreamSerializableUtil.storeStringValueWithNullCheck;

import java.io.IOException;
import java.util.ArrayList;
import java.io.InputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.io.PrintWriter;
import java.io.Serializable;
import java.security.cert.X509Certificate;
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.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;
import com.scytl.products.ov.commons.beans.domain.model.messaging.SafeStreamDeserializationException;
import com.scytl.products.ov.commons.beans.domain.model.messaging.StreamSerializableClassType;

public class ChoiceCodesVerificationDecryptResPayload implements Payload, Serializable {
    private static final long serialVersionUID = -6330961758896513440L;

    private static final Executor executor = Executors.newSingleThreadExecutor();
    
    private List<String> decryptContributionResult;

    private String exponentiationProofJson;

    private String publicKeyJson;
    
    @JsonDeserialize(as = CryptolibPayloadSignature.class)
    private PayloadSignature signature;

    public ChoiceCodesVerificationDecryptResPayload() {
        super();
    }

    public String getExponentiationProofJson() {
        return exponentiationProofJson;
    }

    public void setExponentiationProofJson(String exponentiationProofJson) {
        this.exponentiationProofJson = exponentiationProofJson;
    }

    public List<String> getDecryptContributionResult() {
        return decryptContributionResult;
    }

    public void setDecryptContributionResult(List<String> decryptContributionResult) {
        this.decryptContributionResult = decryptContributionResult;
    }

    public String getPublicKeyJson() {
        return publicKeyJson;
    }

    public void setPublicKeyJson(String publicKeyJson) {
        this.publicKeyJson = publicKeyJson;
    }

    @Override
    public void serialize(MessagePacker packer) throws IOException {
        if(decryptContributionResult == null) {
            packer.packNil();
        } else {
            packer.packArrayHeader(decryptContributionResult.size());
            for (String string : decryptContributionResult) {
                storeStringValueWithNullCheck(packer, string);
            }
        }
        storeStringValueWithNullCheck(packer, exponentiationProofJson);
        storeStringValueWithNullCheck(packer, publicKeyJson);
        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 {
            if (unpacker.tryUnpackNil()) {
                decryptContributionResult = null;
            } else {
                int listSize = unpacker.unpackArrayHeader();
                decryptContributionResult = new ArrayList<>(listSize);
                for (int i = 0; i < listSize; i++) {
                    decryptContributionResult.add(retrieveStringValueWithNullCheck(unpacker));
                }
            }
            exponentiationProofJson = retrieveStringValueWithNullCheck(unpacker);
            publicKeyJson = retrieveStringValueWithNullCheck(unpacker);
            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 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.

        // Output stream upon which the relevant DTO properties are dumped.
        PipedInputStream pis = new PipedInputStream();
        PipedOutputStream pos = new PipedOutputStream(pis);

        // Feed the piped output stream from another thread to prevent deadlocks.
        executor.execute(() -> {
            try (PrintWriter writer = new PrintWriter(pos)) {
                for(String decryptionContributionResultEntry : decryptContributionResult) {
                    writer.write(decryptionContributionResultEntry);
                }
                writer.write(exponentiationProofJson);
                writer.write(publicKeyJson);
            }
        });

        return pis;
    }

    @Override
    public PayloadSignature getSignature() {
        return signature;
    }

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

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

}
