// Path: osgi.enroute.trains.api/src/osgi/enroute/trains/cloud/api/Segment.java
// public class Segment extends DTO {
// 
// 	public enum Type {
// 		STRAIGHT, CURVED, SWITCH, SIGNAL, LOCATOR, BLOCK;
// 	}
// 	
// 
// 	/**
// 	 * Our Identity
// 	 */
// 	public String id;
// 	
// 	/**
// 	 * Type of the segment
// 	 */
//  	public Type type;
// 
// 	/**
//  	* Name of the track.This is the name required to get permission 
//  	* from the Track Manager. If this name starts with X then no admission is required. This 
//  	* is generally for switches.
//  	*/
//  	public String track;
//  	
// 	/**
// 	 * Sequence of the segment in the track
// 	 */
// 	public int sequence;
// 
// 	/**
// 	 * Length of the segment in millimeters, can be zero. It an be zero because
// 	 * the relation between the controllers and the switches and the signals is
// 	 * not always 1:1 mappable to controllers. Therefore zero sized segments can
// 	 * be used to map correctly.
// 	 */
// 	public int length;
// 
// 	/**
// 	 * Short code for RFID tag attached to this segment, or 0 for none.
// 	 */
// 	public int tagCode;
// 
// 	/**
// 	 * The associated controller
// 	 */
// 	public int controller = -1;
// 
// 	/**
// 	 * The next segment. Only a switch can have an additional to. The second to
// 	 * is the one selected if the switch is set to its alternative.
// 	 */
// 	public String to[];
// 	
// 	/**
// 	 * The previous segment. Only a switch can have an additional from. The second from
// 	 * is the one selected if the switch is set to its alternative.
// 	 */
// 	public String from[];
// 
// }
// 
// Path: osgi.enroute.trains.api/src/osgi/enroute/trains/cloud/api/TrackForSegment.java
// public interface TrackForSegment extends TrackInfo {
// 
// 	/**
// 	 * Send when a train has been identified at a locator segment
// 	 * 
// 	 * @param rfid
// 	 *            The RFID for the train
// 	 * @param segment
// 	 *            The name of the segment
// 	 */
// 	void locatedTrainAt(String rfid, String segment);
// 
// 	/**
// 	 * Indicate that a switch has been set
// 	 * 
// 	 * @param segment
// 	 *            the name of the switch segment
// 	 * @param alternative
// 	 *            defines the normal or alternative track
// 	 */
// 	void switched(String segment, boolean alternative);
// 	
// 	/**
// 	 * Indicate that a signal changed color
// 	 * 
// 	 * @param segment
// 	 *            the name of the signal segment
// 	 * @param color
// 	 *            the new signal color
// 	 */
// 	void signal(String segment, Color color);
// 	
// 	/**
// 	 * External indication that a segment is broken 
// 	 */
// 	
// 	void blocked(String segment, String reason, boolean blocked);
// }
// 
// Path: osgi.enroute.trains.api/src/osgi/enroute/trains/controller/api/TrainLocator.java
// public interface TrainLocator {
// 
// 	/**
// 	 * get train location. Promise is resolved when train passes next location.
// 	 * @return colon separated string "trainId:segmentName"
// 	 */
// 	Promise<String> nextLocation();
// }


import java.io.ByteArrayInputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.BiFunction;
import java.util.stream.Collectors;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttCallback;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.ConfigurationPolicy;
import org.osgi.service.component.annotations.Deactivate;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.metatype.annotations.Designate;
import org.osgi.util.promise.Deferred;
import org.osgi.util.promise.Promise;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import osgi.enroute.dto.api.DTOs;
import osgi.enroute.trains.cloud.api.Segment;
import osgi.enroute.trains.cloud.api.TrackForSegment;
import osgi.enroute.trains.controller.api.TrainLocator;
        log.info(fmt, args);
    }

    /**
     * MQTT Callback class
     */
    class MyCallbackHandler implements MqttCallback {
        // display data messages received from broker
        @Override
        public void messageArrived(String topic, MqttMessage msg) throws Exception {
//            info("messageArrived on topic=<{}>: {}", topic, msg);

            byte[] payload = msg.getPayload();
            String json = new String(payload);

            if (!json.startsWith("{")) {
                // json: TRAIN1=DisConnected
                String[] split = json.split("=", 2);
                info("connection: train={}, status={}", split[0], split[1]);
                return;
            }

            // json: {"train":"TRAIN1","location":"010E9EF905","time":"432"}
            @SuppressWarnings("unchecked")
            Map<String, Object> map = dtos.decoder(Map.class).get(new ByteArrayInputStream(payload));
            info("location: {}", map);

            String trainId = (String) map.get("train");
            String location = (String) map.get("location");

// Path: osgi.enroute.trains.api/src/osgi/enroute/trains/cloud/api/Segment.java
// public class Segment extends DTO {
// 
// 	public enum Type {
// 		STRAIGHT, CURVED, SWITCH, SIGNAL, LOCATOR, BLOCK;
// 	}
// 	
// 
// 	/**
// 	 * Our Identity
// 	 */
// 	public String id;
// 	
// 	/**
// 	 * Type of the segment
// 	 */
//  	public Type type;
// 
// 	/**
//  	* Name of the track.This is the name required to get permission 
//  	* from the Track Manager. If this name starts with X then no admission is required. This 
//  	* is generally for switches.
//  	*/
//  	public String track;
//  	
// 	/**
// 	 * Sequence of the segment in the track
// 	 */
// 	public int sequence;
// 
// 	/**
// 	 * Length of the segment in millimeters, can be zero. It an be zero because
// 	 * the relation between the controllers and the switches and the signals is
// 	 * not always 1:1 mappable to controllers. Therefore zero sized segments can
// 	 * be used to map correctly.
// 	 */
// 	public int length;
// 
// 	/**
// 	 * Short code for RFID tag attached to this segment, or 0 for none.
// 	 */
// 	public int tagCode;
// 
// 	/**
// 	 * The associated controller
// 	 */
// 	public int controller = -1;
// 
// 	/**
// 	 * The next segment. Only a switch can have an additional to. The second to
// 	 * is the one selected if the switch is set to its alternative.
// 	 */
// 	public String to[];
// 	
// 	/**
// 	 * The previous segment. Only a switch can have an additional from. The second from
// 	 * is the one selected if the switch is set to its alternative.
// 	 */
// 	public String from[];
// 
// }
// 
// Path: osgi.enroute.trains.api/src/osgi/enroute/trains/cloud/api/TrackForSegment.java
// public interface TrackForSegment extends TrackInfo {
// 
// 	/**
// 	 * Send when a train has been identified at a locator segment
// 	 * 
// 	 * @param rfid
// 	 *            The RFID for the train
// 	 * @param segment
// 	 *            The name of the segment
// 	 */
// 	void locatedTrainAt(String rfid, String segment);
// 
// 	/**
// 	 * Indicate that a switch has been set
// 	 * 
// 	 * @param segment
// 	 *            the name of the switch segment
// 	 * @param alternative
// 	 *            defines the normal or alternative track
// 	 */
// 	void switched(String segment, boolean alternative);
// 	
// 	/**
// 	 * Indicate that a signal changed color
// 	 * 
// 	 * @param segment
// 	 *            the name of the signal segment
// 	 * @param color
// 	 *            the new signal color
// 	 */
// 	void signal(String segment, Color color);
// 	
// 	/**
// 	 * External indication that a segment is broken 
// 	 */
// 	
// 	void blocked(String segment, String reason, boolean blocked);
// }
// 
// Path: osgi.enroute.trains.api/src/osgi/enroute/trains/controller/api/TrainLocator.java
// public interface TrainLocator {
// 
// 	/**
// 	 * get train location. Promise is resolved when train passes next location.
// 	 * @return colon separated string "trainId:segmentName"
// 	 */
// 	Promise<String> nextLocation();
// }


// Path: osgi.enroute.trains.location.provider/src/osgi/enroute/trains/location/provider/TrainLocationClient.java
import java.io.ByteArrayInputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.BiFunction;
import java.util.stream.Collectors;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttCallback;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.ConfigurationPolicy;
import org.osgi.service.component.annotations.Deactivate;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.metatype.annotations.Designate;
import org.osgi.util.promise.Deferred;
import org.osgi.util.promise.Promise;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import osgi.enroute.dto.api.DTOs;
import osgi.enroute.trains.cloud.api.Segment;
import osgi.enroute.trains.cloud.api.TrackForSegment;
import osgi.enroute.trains.controller.api.TrainLocator;

        log.info(fmt, args);
    }

    /**
     * MQTT Callback class
     */
    class MyCallbackHandler implements MqttCallback {
        // display data messages received from broker
        @Override
        public void messageArrived(String topic, MqttMessage msg) throws Exception {
//            info("messageArrived on topic=<{}>: {}", topic, msg);

            byte[] payload = msg.getPayload();
            String json = new String(payload);

            if (!json.startsWith("{")) {
                // json: TRAIN1=DisConnected
                String[] split = json.split("=", 2);
                info("connection: train={}, status={}", split[0], split[1]);
                return;
            }

            // json: {"train":"TRAIN1","location":"010E9EF905","time":"432"}
            @SuppressWarnings("unchecked")
            Map<String, Object> map = dtos.decoder(Map.class).get(new ByteArrayInputStream(payload));
            info("location: {}", map);

            String trainId = (String) map.get("train");
            String location = (String) map.get("location");

            if (location != null) {