package meerkat.pollingstation;

import com.google.protobuf.BoolValue;
import com.google.protobuf.ByteString;
import meerkat.crypto.DigitalSignature;
import meerkat.protobuf.PollingStation;
import meerkat.rest.Constants;
import meerkat.rest.ProtobufMessageBodyReader;
import meerkat.rest.ProtobufMessageBodyWriter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.Response;
import java.security.SignatureException;

import static meerkat.pollingstation.PollingStationConstants.POLLING_STATION_WEB_SCANNER_CONNECT_PATH;
import static meerkat.pollingstation.PollingStationConstants.POLLING_STATION_WEB_SCANNER_SCAN_PATH;

/**
 * Created by Laura on 3/20/2017.
 */
public class ScannerClientAPI implements PollingStationScanner.ScannerClient {
    final Logger logger = LoggerFactory.getLogger(getClass());

    DigitalSignature signer;
    ByteString scannerID;
    Client client;
    WebTarget baseWebTarget;

    /**
     * Serial number of request (zeroed on new connection to server)
     */
    long serial;


    protected ScannerClientAPI() {
        this.signer = null;
        this.scannerID = null;
        this.client = ClientBuilder.newClient();
        this.client.register(ProtobufMessageBodyReader.class);
        this.client.register(ProtobufMessageBodyWriter.class);
    }

    /**
     * Constructor for client that doesn't use signatures.
     * @param scannerId
     */
    public ScannerClientAPI(byte[] scannerId) {
        this();
        ByteString.copyFrom(scannerId);
    }

    /**
     * Constructor for client that signs messages.
     * @param signer
     */
    public ScannerClientAPI(DigitalSignature signer) {
        this();
        assert (signer != null && signer.getSignerID() != null); // Use the default constructor instead

        this.signer = signer;
        this.scannerID = signer.getSignerID();
    }


    public void close() {
        client.close();
    }

    @Override
    public boolean connect(PollingStation.ConnectionServerData serverData) {
        serial = 0;
        baseWebTarget = client.target(serverData.getServerUrl());
        PollingStation.ConnectionClientData.Builder clientData = PollingStation.ConnectionClientData.newBuilder()
                .setNonce(serverData.getNonce());

        if (signer != null) {
            clientData.setScannerPK(signer.getSignerPublicKey());
        } else {
            clientData.setScannerId(scannerID);
        }

        Response response = baseWebTarget.path(POLLING_STATION_WEB_SCANNER_CONNECT_PATH)
                .request(Constants.MEDIATYPE_PROTOBUF)
                .post(Entity.entity(clientData.build(), Constants.MEDIATYPE_PROTOBUF));
        BoolValue res = response.readEntity(BoolValue.class);
        response.close();
        return res.getValue();
    }

    boolean sendScannedData(PollingStation.ScannedData.Builder scannedData) {

        scannedData.setSerial(serial++)
                .setScannerId(scannerID);

        PollingStation.SignedScannedData.Builder signedDataBuilder = PollingStation.SignedScannedData.newBuilder()
                .setData(scannedData);

        if (signer != null) {
            try {
                signer.updateContent(scannedData.build());

                signedDataBuilder.setScannerSig(signer.sign());
            } catch (SignatureException e) {
                throw new RuntimeException(e);
            }
        }

        Response response = baseWebTarget.path(POLLING_STATION_WEB_SCANNER_SCAN_PATH)
                .request(Constants.MEDIATYPE_PROTOBUF)
                .post(Entity.entity(signedDataBuilder.build(), Constants.MEDIATYPE_PROTOBUF));
        BoolValue res = response.readEntity(BoolValue.class);
        response.close();
        return res.getValue();
    }

    /**
     * Sends the scanned data to the polling station server
     * @param scannedBallot
     * @return boolean value: true if the data was sent successfully, false otherwise
     */
    @Override
    public boolean newScan(PollingStation.ScannedBallot scannedBallot) {
        PollingStation.ScannedData.Builder scannedData = PollingStation.ScannedData.newBuilder()
                .setBallot(scannedBallot);

        return sendScannedData(scannedData);
    }

    @Override
    public boolean reportError(PollingStation.ScanError scanError) {
        PollingStation.ScannedData.Builder scannedData = PollingStation.ScannedData.newBuilder()
                .setError(scanError);

        return sendScannedData(scannedData);
    }

}
