package meerkat.voting.gui.output;

import com.google.protobuf.ByteString;
import com.google.protobuf.InvalidProtocolBufferException;
import com.google.zxing.WriterException;
import meerkat.protobuf.Crypto.EncryptionRandomness;
import meerkat.protobuf.Crypto.RandomnessGenerationProof;
import meerkat.protobuf.PollingStation;
import meerkat.protobuf.PollingStation.ScannedData;
import meerkat.protobuf.Voting.BallotSecrets;
import meerkat.protobuf.Voting.PlaintextBallot;
import meerkat.protobuf.Voting.SignedEncryptedBallot;
import meerkat.rest.Constants;
import meerkat.voting.gui.controllersFX.VistaNavigator;
import meerkat.voting.output.AsyncRunnableOutputDevice;
import meerkat.voting.output.outputcommands.AuditOutputCommand;
import meerkat.voting.output.outputcommands.CancelOutputCommand;
import meerkat.voting.output.outputcommands.CastOutputCommand;
import meerkat.voting.output.outputcommands.CommitOutputCommand;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.ws.rs.client.Entity;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Base64;

/**
 * A toy OutputDevice class
 * outputs everything simply to the System console
 */
public class FXWindowOutputDevice extends AsyncRunnableOutputDevice {

    private static final Logger logger = LoggerFactory.getLogger(FXWindowOutputDevice.class);

    public FXWindowOutputDevice() {
        super();
        logger.info("A FXWindowOutputDevice is constructed");
    }

    /**
     * Committing to the ballot.
     * Simply prints to the output stream all the details in the CommitOutputCommand.
     * @param command details to commit to, and the callback to call when finished
     */
    public void doCommitToBallot(CommitOutputCommand command) {
        logger.debug("entered method doCommitToBallot");
        PlaintextBallot plaintextBallot = command.getPlaintext();
        ByteString channel = command.getChannelIdentifierByteString();
        printPlainText(plaintextBallot, channel);

        printEncryptedText(plaintextBallot, command);
    }


    /**
     * auditing the ballot.
     * prints to the output stream the ballot secrets (the encryption randomness and its proof of random generation)
     * @param command An auditing command with the callback to finally call
     */
    public void doAudit(AuditOutputCommand command) {
        logger.debug("entered method doAudit");
        String msg = "Auditing";
        VistaNavigator.printFX(msg);
        BallotSecrets ballotSecrets = command.getBallotSecrets();
        printEncryptionRandomness(ballotSecrets.getEncryptionRandomness());
        printRandomnessGenerationProof (ballotSecrets.getProof());
        command.getCallback().onSuccess(null);
    }

    /**
     * Casting the ballot (actually does nothing new)
     * @param command a CastOutputCommand with the details and the callback
     */
    public void doCastBallot(CastOutputCommand command) {
        logger.debug("entered method doCastBallot");
        String msg = "Ballot finalized for casting!";
        VistaNavigator.printFX(msg);
        command.getCallback().onSuccess(null);
    }


    /**
     * Canceling the ballot (actually does nothing new)
     * @param command a CancelOutputCommand with the details and the callback
     */
    public void doCancel(CancelOutputCommand command) {
        logger.debug("entered method doCancel");
        System.out.println("Ballot cancelled!");
        command.getCallback().onSuccess(null);
    }

    private void printPlainText(PlaintextBallot plaintextBallot, ByteString channel) {
        long plaintextSerialNumber = plaintextBallot.getSerialNumber();
        String toPrint = "";
        toPrint+="Commitment of Ballot #" + plaintextSerialNumber;
        toPrint+="\n";
        toPrint+="(channel): ";
        toPrint+="\n";
        toPrint+=bytesToString(channel);
        toPrint+="\n";
        toPrint+="(plaintext): ";
        toPrint+="\n";
        toPrint+=plaintextBallot;
        VistaNavigator.printFX(toPrint);
    }

    private void printEncryptedText(PlaintextBallot plaintextBallot, CommitOutputCommand command) {
        String encryptedText;
        SignedEncryptedBallot signedEncryptedBallot = command.getSignedEncryptedBallot();
        long encryptedSerialNumber = signedEncryptedBallot.getEncryptedBallot().getSerialNumber();
        long plaintextSerialNumber = plaintextBallot.getSerialNumber();

        String toPrint = "";
        toPrint+="Commitment of Ballot #" + encryptedSerialNumber + " (ciphertext):";
        toPrint+="\n";
        VistaNavigator.printFX(toPrint);
        if (plaintextSerialNumber != encryptedSerialNumber) {
            logger.error("plaintext and encryption serial numbers do not match!! plaintext# = " +
                    plaintextSerialNumber + ", ciphertext# = " + encryptedSerialNumber);
        }

        PollingStation.ScannedBallot scannedBallot = PollingStation.ScannedBallot.newBuilder()
                .setChannel(command.getChannelIdentifierByteString())
                .setSignedEncryptedBallot(command.getSignedEncryptedBallot())
                .build();

        System.out.println(scannedBallot.getSerializedSize());
        encryptedText = Base64.getEncoder().encodeToString(scannedBallot.toByteArray());

        try {
            PollingStation.ScannedBallot result = PollingStation.ScannedBallot.parseFrom(Base64.getDecoder().decode(encryptedText.getBytes()));
            System.out.println(result.getChannel());
            System.out.println(command.getChannelIdentifierByteString());
        } catch (InvalidProtocolBufferException e) {
            e.printStackTrace();
        }

        try {
            printQRCode(encryptedText, 250);
            command.getCallback().onSuccess(null);
        } catch (IOException e) {
            command.getCallback().onFailure(e);
        } catch (WriterException e) {
            command.getCallback().onFailure(e);
        }

    }

    private void printQRCode(String toQRCode, int size) throws IOException, WriterException {
        BufferedImage image = null;
        image = GenerateQRCode.createQRImage(toQRCode, size);
        VistaNavigator.printFX(image);
    }

    private void printEncryptionRandomness (EncryptionRandomness encryptionRandomness) {
        String msg = "Encryption Randomness = ";
        ByteString data = encryptionRandomness.getData();
        msg+=bytesToString(data);
        VistaNavigator.printFX(msg);
    }

    private void printRandomnessGenerationProof (RandomnessGenerationProof proof) {
        String msg = "Proof of randomness generation:";
        ByteString data = proof.getData();
        msg+=bytesToString(data);
        VistaNavigator.printFX(msg);
    }


    /*
     * Returns the UTF8 decoding of byte-string data
     */
    private static String bytesToString(ByteString data) {
        return data.toStringUtf8();
    }

}
