package org.jivesoftware.openfire.plugin.gojara.sessions;

import java.text.DecimalFormat;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import org.jivesoftware.openfire.PacketRouter;
import org.jivesoftware.openfire.XMPPServer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xmpp.packet.JID;
import org.xmpp.packet.Message;
import org.xmpp.packet.Message.Type;

/**
 * This Class sends Ad-Hoc commands to given spectrum2 transports, which will then be intercepted by MainInterceptor,
 * and processed by GojaraAdminProcessor
 * 
 * @author axel.frederik.brand
 * 
 */
public class GojaraAdminManager {
	private static GojaraAdminManager myself;
	private PacketRouter router;
	private static final Logger Log = LoggerFactory.getLogger(GojaraAdminManager.class);
	private JID adminUser;
	private XMPPServer _server;
	private Set<String> unconfiguredGateways;
	private Set<String> configuredGateways;

	private Map<String, Map<String, Integer>> gatewayStatisticsMap;
	private long refreshCooldown = 0;

	private GojaraAdminManager() {
		_server = XMPPServer.getInstance();
		router = _server.getPacketRouter();

		// we dont actually need to CREATE the user, it's sufficient to create the jid. The message gets intercepted before its processed.
		adminUser = _server.createJID("gojaraadmin", null);

		unconfiguredGateways = new HashSet<String>();
		configuredGateways = new HashSet<String>();
		gatewayStatisticsMap = new ConcurrentHashMap<String, Map<String, Integer>>(16, 0.75f, 1);
	}

	public static GojaraAdminManager getInstance() {
		if (myself == null) {
			myself = new GojaraAdminManager();
		}
		return myself;
	}
	
	/**
	 * Sends a testmessage to specified gateway, when a response gets intercepted we consider gateway configured
	 * 
	 */
	public void testAdminConfiguration(String gateway) {
		unconfiguredGateways.add(gateway);
		Message message = generateCommand(gateway, "config_check");
		message.setBody("status");
		router.route(message);
		Log.info("Checking for admin configuration on " + gateway);
	}

	/**
	 * Gets called from Interceptor to confirm that a Gateway responded to our config_check message.
	 * 
	 * @param gateway
	 */
	public void confirmGatewayConfig(String gateway) {
		unconfiguredGateways.remove(gateway);
		configuredGateways.add(gateway);
		gatewayStatisticsMap.put(gateway, new ConcurrentHashMap<String, Integer>(16, 0.75f, 1));
		gatherGatewayStatistics(gateway);
	}

	/**
	 * If a gateway disconnects we have to check if it was not configured as we may want to alter boolean
	 * areGatewaysConfigured.
	 */
	public void gatewayUnregistered(String gateway) {
		unconfiguredGateways.remove(gateway);
		configuredGateways.remove(gateway);
		gatewayStatisticsMap.remove(gateway);
	}

	public boolean areGatewaysConfigured() {
		return unconfiguredGateways.isEmpty();
	}

	public boolean isGatewayConfigured(String gateway) {
		return configuredGateways.contains(gateway);
	}

	/**
	 * Generates a basic ad-hoc command with From,To, Type, ID & Body configured. Body might need to be reconfigured
	 * when additional info has to be specified, like unregister
	 * 
	 * @param transport
	 * @param command
	 * @return
	 */
	private Message generateCommand(String transport, String command) {
		Message message = new Message();
		message.setFrom(adminUser);
		message.setTo(transport);
		message.setID(command);
		message.setBody(command);
		message.setType(Type.chat);
		return message;
	}

	/**
	 * Sends the command online_users to specified Spectrum2 transport. We set the ID specific to the command so we can
	 * identify the response. Transport has to be configured for admin_jid = gojaraadmin@domain
	 * 
	 * @param transport
	 */
	public void getOnlineUsersOf(String transport) {
		// no use in sending the message if not configured for gojaraadmin
		if (unconfiguredGateways.contains(transport))
			return;

		Message message = generateCommand(transport, "online_users");
		router.route(message);
		Log.debug("Sent online_users Packet!" + message.toString());
	}

	/**
	 * Sends the unregister <bare_jid> command to specified Spectrum2 transport. We set the ID specific to the command
	 * so we can identify the response. Transport has to be configured for admin_jid = gojaraadmin@domain
	 * 
	 * @param transport
	 */
	public void unregisterUserFrom(String transport, String user) {
		if (unconfiguredGateways.contains(transport))
			return;

		Message message = generateCommand(transport, "unregister");
		message.setBody("unregister " + _server.createJID(user, null).toString());
		router.route(message);
		Log.debug("Sent Unregister Packet!" + message.toString());

	}

	public void putStatisticValue(String subdomain, String statistic, int value) {
		gatewayStatisticsMap.get(subdomain).put(statistic, value);
	}

	/**
	 * For JSP usage, dont refresh statistics more than once a minute
	 */
	public void gatherGatewayStatistics() {
		if (refreshCooldown == 0) {
			refreshCooldown = System.currentTimeMillis();
		} else if ((System.currentTimeMillis() - refreshCooldown) < 60000) {
			// once a minute max
			return;
		}

		refreshCooldown = System.currentTimeMillis();

		for (String gateway : configuredGateways) {
			gatherGatewayStatistics(gateway);
		}
	}

	/**
	 * gathers Gatewaystatistics for specific gateway
	 * 
	 * @param gateway
	 */
	public void gatherGatewayStatistics(String gateway) {
		uptime(gateway);
		messagesFrom(gateway);
		messagesTo(gateway);
		usedMemoryOf(gateway);
		averageMemoryOfUser(gateway);
		// Log.info("Gathering Gateway-Statistics for " + gateway);
	}

	private void uptime(String transport) {
		Message message = generateCommand(transport, "uptime");
		router.route(message);
	}

	private void messagesFrom(String transport) {
		Message message = generateCommand(transport, "messages_from_xmpp");
		router.route(message);
	}

	private void messagesTo(String transport) {
		Message message = generateCommand(transport, "messages_to_xmpp");
		router.route(message);
	}

	private void usedMemoryOf(String transport) {
		Message message = generateCommand(transport, "used_memory");
		router.route(message);
	}

	private void averageMemoryOfUser(String transport) {
		Message message = generateCommand(transport, "average_memory_per_user");
		router.route(message);
	}

	/**
	 * Preps the specified stat for nicer output which is used in tables.
	 * 
	 * @param gateway
	 * @param stat
	 * @return
	 */
	public String getStatisticsPresentationString(String gateway, String stat) {
		if (gatewayStatisticsMap.containsKey(gateway)) {
			if (stat.equals("uptime")) {
				if (gatewayStatisticsMap.get(gateway).get("uptime") != null) {
					int time = gatewayStatisticsMap.get(gateway).get("uptime");
					long diffSeconds = time % 60;
					long diffMinutes = time / 60 % 60;
					long diffHours = time / (60 * 60) % 24;
					long diffDays = time / (24 * 60 * 60);
					
					String seconds = diffSeconds == 0 ? "" : (diffSeconds + " Sec");
					String minutes = diffMinutes == 0 ? "" : (diffMinutes + " Min ");
					String hours = diffHours == 0 ? "" : (diffHours + " Hours ");
					String days = diffDays == 0 ? "" : (diffDays + " Days ");
					
					return days + hours + minutes + seconds;
				}
			} else if (stat.equals("messages_from_xmpp")) {
				if (gatewayStatisticsMap.get(gateway).get("messages_from_xmpp") != null)
					return "" + gatewayStatisticsMap.get(gateway).get("messages_from_xmpp");
			} else if (stat.equals("messages_to_xmpp")) {
				if (gatewayStatisticsMap.get(gateway).get("messages_to_xmpp") != null)
					return "" + gatewayStatisticsMap.get(gateway).get("messages_to_xmpp");
			} else if (stat.equals("used_memory")) {
				if (gatewayStatisticsMap.get(gateway).get("used_memory") != null) {
					DecimalFormat f = new DecimalFormat("#0.00");
					double mb = gatewayStatisticsMap.get(gateway).get("used_memory") / 1024.0;

					return "" + f.format(mb) + " MB";
				}
			} else if (stat.equals("average_memory_per_user")) {
				if (gatewayStatisticsMap.get(gateway).get("average_memory_per_user") != null) {
					return "" + gatewayStatisticsMap.get(gateway).get("average_memory_per_user") + " KB";
				}
			}
		}
		return "-";
	}

}
