package de.serosystems.proto.v3.backend.api;

import io.grpc.*;

import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.UUID;

/**
 * This example shows how to use the SecureTrack backend API in
 * a blocking (synchronous) fashion.
 *
 * @author Matthias Schäfer (schaefer@sero-systems.de)
 */
public class BlockingExample {

    private static final String API_ADDRESS = "api.secureadsb.com";
    private static final int API_PORT = 4201;
    private static final UUID token = UUID.fromString("INSERT YOUR TOKEN HERE");
    private static final String TLS_CA = "ca.crt"; // point this to your ca.crt

    private static final long DATA_TIMEOUT_MS = 15_000; // 15 seconds

    public static void printSensorInfo(Collection<SeRoAPIProto.SensorInformation> sensors) {
        // print response
        System.out.printf("We got information about %d sensors:\n", sensors.size());

        for (SeRoAPIProto.SensorInformation info : sensors)
            // check if location is available
            switch (info.getGnss().getPosition().getFixType()) {
                case Pos2D:
                case Pos3D:
                case TimeOnly:
                    // sensor location available
                    System.out.printf("\t%d (%s, alias %s) at %.4f,%.4f.\n",
                            info.getSensor().getSerial(), info.getSensor().getType(), info.getAlias(),
                            info.getGnss().getPosition().getLatitude(), info.getGnss().getPosition().getLongitude());
                    break;
                case None:
                case UNRECOGNIZED:
                    // sensor location unknown
                    System.out.printf("\t%d (%s alias %s) at an unknown location.\n",
                            info.getSensor().getSerial(), info.getSensor().getType(), info.getAlias());
                    break;
            }
    }

    public static void printTargetReports(Collection<SeRoAPIProto.TargetReport> reports) {
        // some counters
        int adsb = 0, mlat = 0, both = 0, none = 0;
        int noADSB = 0, ADSBv0 = 0, ADSBv1 = 0, ADSBv2 = 0, ADSBv3 = 0;
        boolean hasValidADSB, hasValidMLAT;
        for (SeRoAPIProto.TargetReport t : reports) {
            // do we have recent ADS-B info?
            hasValidADSB = t.hasAdsb() &&
                    System.currentTimeMillis() - t.getAdsb().getPositionLastSeen() < DATA_TIMEOUT_MS;

            // do we have recent MLAT info?
            hasValidMLAT = t.hasMlat() &&
                    System.currentTimeMillis() - (t.getMlat().getTxTimestamp() / 1_000_000L) < DATA_TIMEOUT_MS;

            // do we have ADS-B information on the target and is it up to date?
            if (hasValidADSB) {
                adsb++;

                // count versions
                switch (t.getAdsb().getAdsbVersion()) {
                    case 0:
                        ADSBv0++;
                        break;
                    case 1:
                        ADSBv1++;
                        break;
                    case 2:
                        ADSBv2++;
                        break;
                    case 3:
                        ADSBv3++;
                        break;
                    default: // ignore
                }
            } else noADSB++;

            // do we have MLAT information on the target and is it up to date?
            if (hasValidMLAT)
                mlat++;

            if (hasValidADSB && hasValidMLAT)
                both++;

            if (!hasValidADSB && !hasValidMLAT)
                none++;
        }

        System.out.printf("%d targets were equipped with ADS-B.\n", adsb);
        System.out.printf("%d targets were not tracked with ADS-B.\n", noADSB);
        System.out.printf("%d targets were tracked by MLAT.\n", mlat);
        System.out.printf("%d targets were tracked by both ADS-B and MLAT.\n", both);
        System.out.printf("%d targets had neither valid ADS-B nor MLAT info.\n", none);

        System.out.printf("Out of the %d ADS-B equipped targets, we saw the following versions:\n", adsb);
        System.out.printf("\t%d had ADS-B version 0 transponders\n", ADSBv0);
        System.out.printf("\t%d had ADS-B version 1 transponders\n", ADSBv1);
        System.out.printf("\t%d had ADS-B version 2 transponders\n", ADSBv2);
        System.out.printf("\t%d had ADS-B version 3 transponders\n", ADSBv3);
    }

    public static void main(String[] args) {

        System.out.println("Hello world! This is an example for using the SecureTrack API with blocking gRPC calls!");

        System.out.printf("Connecting to SeRo API at %s:%d.\n", API_ADDRESS, API_PORT);

        // first create channel
        final ChannelCredentials credentials;
        try {
            credentials = TlsChannelCredentials.newBuilder()
                    .trustManager(new File(TLS_CA))
                    .build();
        } catch (IOException e) {
            System.err.println("Could not load TLS certificate: " + e.getMessage());
            return;
        }
        ManagedChannel ch = Grpc.newChannelBuilderForAddress(API_ADDRESS, API_PORT, credentials)
                .build();

        // then create stub
        SeRoAPIGrpc.SeRoAPIBlockingStub stub = SeRoAPIGrpc.newBlockingStub(ch);

        System.out.println("Retrieving info about all sensors that are available to this token.");

        // prepare request
        SeRoAPIProto.SensorInfoRequest sensorRequest = SeRoAPIProto.SensorInfoRequest.newBuilder()
                .setToken(token.toString())
                .build();

        // retrieve response
        SeRoAPIProto.SensorInfoResponse sensorResponse = stub.getSensorInfo(sensorRequest);

        // print response
        printSensorInfo(sensorResponse.getSensorInfoList());

        System.out.println("Let's see what happens when we request info for a sensor that does not belong to us!");

        sensorRequest = SeRoAPIProto.SensorInfoRequest.newBuilder()
                .setToken(token.toString())
                .addSensors(SeRoAPIProto.Sensor.newBuilder()
                        .setSerial(12345L)
                        .setType(SeRoAPIProto.Sensor.Type.GRX1090)
                        .build())
                .build();

        try {
            stub.getSensorInfo(sensorRequest);
            System.out.println("It worked?!"); // you shouldn't see this message
        } catch (StatusRuntimeException e) {
            System.out.printf("Retrieving sensor info failed (%s).\n", e.getStatus().getCode());
            System.out.println("Cause was: " + e.getStatus().getDescription());
        }

        System.out.println("Now we are going to retrieve target state reports for about 10 seconds.");

        SeRoAPIProto.TargetReportsRequest targetReportsRequest = SeRoAPIProto.TargetReportsRequest.newBuilder()
                .setToken(token.toString())
                .build();

        // some data handling
        HashMap<Integer, SeRoAPIProto.TargetReport> targetReports = new HashMap<>();
        long start = System.currentTimeMillis();
        long count = 0L;

        // open stream
        Iterator<SeRoAPIProto.TargetReport> it = stub.getTargetReports(targetReportsRequest);
        while (it.hasNext() && System.currentTimeMillis() - start < 10_000L) {
            SeRoAPIProto.TargetReport target = it.next();
            count++;

            // store in target map (replaces older data of the same target)
            targetReports.put(target.getTarget().getAddress(), target);
        }

        System.out.printf("Done! We received %d target reports from %d different aircraft!\n",
                count, targetReports.size());

        printTargetReports(targetReports.values());

        // close connection gracefully
        ch.shutdownNow();

    }

}
