package de.hottis.MeasurementCollector;

import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Properties;

import javax.management.RuntimeErrorException;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttCallbackExtended;
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;



public class MqttReceiver {
	final protected Logger logger = LogManager.getRootLogger();

	static final String MQTT_BROKER_PROP = "mqtt.broker";
	static final String MQTT_CLIENTID_PROP = "mqtt.clientid";
	static final String MQTT_USERNAME_PROP = "mqtt.username";
	static final String MQTT_PASSWORD_PROP = "mqtt.password";
	
	
	class Callback implements MqttCallbackExtended {
		public void messageArrived(String topic, MqttMessage payload) {
			parsers.get(topic).execute(LocalDateTime.now(), payload.toString());
		}
		
		public void connectComplete(boolean reconnect, java.lang.String serverURI) {
			logger.info("Connection established for " + serverURI);
		}
		
		public void connectionLost(java.lang.Throwable cause) {
			logger.error("Connection lost, cause: " + cause.toString());
			reconnect();
		}
		
		public void deliveryComplete(IMqttDeliveryToken token) {
			
		}
	}
	
	private final String broker;
	private final String clientId;
	private final MqttConnectOptions connOpts;
	private MqttClient client;
	private HashMap<String, AMessageParser> parsers;
	private final Callback callback = new Callback();
	
	public MqttReceiver(Properties config) {
		broker = config.getProperty(MQTT_BROKER_PROP, "localhost");
		clientId = config.getProperty(MQTT_CLIENTID_PROP, "MeasurementCollector.MqttReceiver");
		connOpts = new MqttConnectOptions();
		String username = config.getProperty(MQTT_USERNAME_PROP);
		String password = config.getProperty(MQTT_PASSWORD_PROP);
		if (username != null && password != null) {
			connOpts.setUserName(username);
			connOpts.setPassword(password.toCharArray());
		}
		
		parsers = new HashMap<String, AMessageParser>();
	}
	
	public void connect() throws MeasurementCollectorException {
		try {
			client = new MqttClient(broker, clientId);
			client.setCallback(callback);
			client.connect(connOpts);
			logger.info("Connected");
		} catch (MqttException e) {
			throw new MeasurementCollectorException("MqttReceiver.connect", e);
		}
	}
	
	public void reconnect() {
		logger.error("reconnect called");
		if (! client.isConnected()) {
			while (true) {
				try {
					client.connect(connOpts);
					for (String topic : parsers.keySet()) {
						client.subscribe(topic);
						logger.info("Re-Subscribed: " + topic);
					}
					logger.error("reconnecting successfully completed");
					break;
				} catch (MqttException e) {
					logger.error("Exception during reconnection: " + e.toString());
					try {
						Thread.sleep(10*1000);
					} catch (InterruptedException e1) {
					}
				}
			}
		} else {
			logger.error("client is still connected");
		}
	}
	
	public void registerParser(AMessageParser parser) throws MeasurementCollectorException {
		try {
			parsers.put(parser.getTopic(), parser);
			client.subscribe(parser.getTopic());
			logger.info("Subscribed: " + parser.getTopic());
		} catch (MqttException e) {
			throw new MeasurementCollectorException("MqttReceiver.registerParser", e);
		}
	}
}
