/*
 * Copyright (c) 2005-2011 KOM - Multimedia Communications Lab
 *
 * This file is part of PeerfactSim.KOM.
 *
 * PeerfactSim.KOM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * any later version.
 *
 * PeerfactSim.KOM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with PeerfactSim.KOM.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

package de.tud.kom.p2psim.impl.util.oracle;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import de.tud.kom.p2psim.api.common.SimHost;
import de.tudarmstadt.maki.simonstrator.api.Graphs;
import de.tudarmstadt.maki.simonstrator.api.Host;
import de.tudarmstadt.maki.simonstrator.api.Randoms;
import de.tudarmstadt.maki.simonstrator.api.common.graph.Graph;
import de.tudarmstadt.maki.simonstrator.api.common.graph.IEdge;
import de.tudarmstadt.maki.simonstrator.api.common.graph.INode;
import de.tudarmstadt.maki.simonstrator.api.component.ComponentNotAvailableException;
import de.tudarmstadt.maki.simonstrator.api.component.core.OracleComponent;
import de.tudarmstadt.maki.simonstrator.api.component.network.NetID;
import de.tudarmstadt.maki.simonstrator.api.component.network.NetInterface;
import de.tudarmstadt.maki.simonstrator.api.component.sensor.location.IAttractionPoint;
import de.tudarmstadt.maki.simonstrator.api.component.topology.TopologyID;
import de.tudarmstadt.maki.simonstrator.api.component.topology.TopologyProvider;

/**
 * This class gives access to the hosts of the scenario. To work, it has to be
 * referenced in the configuration file after the host builder.
 *
 * The purpose of this class is to enable a global knowledge for analyzing. It
 * is not meant to be used within any functional parts of simulated systems.
 *
 * @author Julius Rueckert
 *
 */
public class GlobalOracle implements OracleComponent {

	private static HashMap<Long, SimHost> hostIDtoHosts = new LinkedHashMap<Long, SimHost>();

	private static HashMap<NetID, SimHost> netIDtoHosts = new LinkedHashMap<NetID, SimHost>();

	private static List<SimHost> hosts = new LinkedList<SimHost>();

	private static List<NetID> bootstrapList = new LinkedList<NetID>();

	private static GlobalOracle instance = new GlobalOracle();
	
	private static Set<IAttractionPoint> attractionPoints = new LinkedHashSet<IAttractionPoint>();

	private GlobalOracle() {
		//
	}

	public static GlobalOracle getInstance() {
		return instance;
	}
	
	public static void addAttractionPoint(IAttractionPoint attractionPoint) {
		GlobalOracle.attractionPoints.add(attractionPoint);
	}
	
	public static void removeAttractionPoint(IAttractionPoint attractionPoint) {
		GlobalOracle.attractionPoints.remove(attractionPoint);
	}
	
	public static Set<IAttractionPoint> getAttractionPoints() {
		return attractionPoints;
	}

	/**
	 * Initial population with hosts.
	 *
	 * @param hostBuilder
	 */
	public static void populate(List<SimHost> allHosts) {
		hosts = allHosts;

		if (hosts == null || hosts.size() <= 0) {
			return;
		}

		for (SimHost host : hosts) {

			/* Might happen in case of FakeHost. */
			if (host.getNetworkComponent() == null) {
				continue;
			}

			hostIDtoHosts.put(host.getHostId(), host);
			for (NetInterface net : host.getNetworkComponent()
					.getNetworkInterfaces()) {
				netIDtoHosts.put(net.getLocalInetAddress(), host);
				bootstrapList.add(net.getLocalInetAddress());
			}
		}

		return;
	}

	/**
	 * Sets the bootstrap hosts. To be called by netLayer.
	 *
	 * @param bootstrapList
	 *            the new bootstrap hosts
	 */
	public static void setBootstrapHosts(List<NetID> bootstrapList) {
		GlobalOracle.bootstrapList = bootstrapList;
	}

	/**
	 * Gets the bootstrap hosts.
	 *
	 * @return the bootstrap hosts
	 */
	public static List<NetID> getBootstrapHosts() {
		return GlobalOracle.bootstrapList;
	}

	/**
	 * Gets the random host.
	 *
	 * @return the random host
	 */
	public static NetID getRandomHost() {
		return bootstrapList.get(Randoms.getRandom(GlobalOracle.class)
				.nextInt() % bootstrapList.size());
	}

	/**
	 * Gets the first host.
	 *
	 * @return the first host
	 */
	public static NetID getFirstHost() {

		if (bootstrapList.size() == 0) {
			assert (false) : "Bootstraplist is empty";
			return null;
		}

		return bootstrapList.get(0);

	}

	/**
	 * @param id
	 * @return the host with the given <code>NetID</code>
	 */
	public static SimHost getHostForNetID(NetID id) {
		return netIDtoHosts.get(id);
	}

	/**
	 * @param id
	 * @return the host with the given host ID
	 */
	public static SimHost getHostForHostID(Long id) {
		return hostIDtoHosts.get(id);
	}

	/**
	 * @return the list with all hosts of the scenario
	 */
	public static List<SimHost> getHosts() {
		synchronized (hosts) {
			return new ArrayList<SimHost>(hosts);
		}
	}

	/**
	 * @return the list with all hosts of the scenario
	 */
	public static List<SimHost> getHostsByGroupID(String groupID) {
		
		if(groupID.equals("")) {
			return getHosts();
		}
		else {
			synchronized (hosts) {
				ArrayList<SimHost> groupedHosts = new ArrayList<SimHost>();
				
				for (SimHost h : hosts) {
					if(h.getProperties().getGroupID().equals(groupID)) {
						groupedHosts.add(h);
					}
				}
				
				return groupedHosts;
			}
		}	
	}
	
	@Override
	public List<Host> getAllHosts() {
		return new ArrayList<Host>(hosts);
	}

	@Override
	public Host getHostByID(long pValue) {
	    return hostIDtoHosts.get(pValue);
	}

	@Override
	public boolean isSimulation() {
		return true;
	}

	/**
	 * Returns a global view of the topology for the specified mechanism. The
	 * mechanism must be a HostComponent that is registered at the local host.
	 * Otherwise, this method will not be able to find the local mechanism
	 * objects.
	 *
	 * @param component
	 * @param identifier
	 * @return
	 */
	public static <T extends TopologyProvider> Graph getTopology(
			Class<T> component, TopologyID identifier) {

		HashSet<IEdge> edges = new LinkedHashSet<>();
		HashSet<INode> nodes = new LinkedHashSet<>();

		LinkedList<TopologyProvider> allProviders = new LinkedList<>();

		for (SimHost host : getHosts()) {
			try {
				TopologyProvider topologyProvider = host
						.getComponent(component);

				INode providerNode = topologyProvider.getNode(identifier);

				nodes.add(providerNode);

				allProviders.add(topologyProvider);

			} catch (ComponentNotAvailableException e) {
				// if the component is not available on the host, we can't do
				// anything about it
				// no reason to crash the simulation as this might be the case
				// in various scenarios
			}
		}

		for (TopologyProvider topologyProvider : allProviders) {
			Set<IEdge> neighbors = topologyProvider.getNeighbors(identifier);
			edges.addAll(neighbors);
		}

		/*
		 * Copy elements and (!) attached properties
		 */
		Graph graph = Graphs.createGraph();
		for(INode node : nodes){
			INode copy = graph.createNode(node.getId());
			copy.addPropertiesFrom(node);
		}
		for(IEdge edge : edges) {
			IEdge copy = graph.createEdge(edge.fromId(), edge.toId());
			copy.addPropertiesFrom(edge);
		}

		return graph;
	}

	/**
	 * Returns available topology identifiers for the given component. Throws an
	 * {@link ComponentNotAvailableException} if the component is not available
	 * on any node in the network. Assumes that all instances of a given
	 * component class provide the same topology identifiers.
	 *
	 * @throws ComponentNotAvailableException
	 */
	public static <T extends TopologyProvider> Iterable<TopologyID> getTopologyIdentifiers(
			Class<T> component) throws ComponentNotAvailableException {

		// iterate over all the hosts, find one host that contains the given
		// component class and ask this component about available topologies
		for (SimHost host : getHosts()) {
			try {
				TopologyProvider topologyProvider = host
						.getComponent(component);
				return topologyProvider.getTopologyIdentifiers();

			} catch (ComponentNotAvailableException e) {
				// if the component is not available on the host, we can't do
				// anything about it
				// no reason to crash the simulation as this might be the case
				// in various scenarios
			}
		}

		throw new ComponentNotAvailableException();
	}



	/**
	 * Checks whether the host with the given NetID is online using a global
	 * list of all hosts in the current scenario.
	 *
	 * @param receiver
	 * @return true if online
	 */
	public static boolean isHostOnline(NetID receiver) {
		if (netIDtoHosts.get(receiver) == null)
			return false;

		return netIDtoHosts.get(receiver).getNetworkComponent()
				.getByNetId(receiver).isUp();
	}
}
