/*
 * Created on Mar 7, 2004
 */
package org.lucci.madhoc.network;

import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Vector;

import org.lucci.madhoc.bining.Cel;
import org.lucci.madhoc.env.NetworkEnvironment;
import org.lucci.madhoc.messaging.Message;
import org.lucci.madhoc.network.net.NetworkInterface;
import org.lucci.madhoc.network.net.NetworkingTechnology;
import org.lucci.madhoc.simulation.Configurable;
import org.lucci.madhoc.simulation.Monitor;
import org.lucci.madhoc.simulation.Simulation;
import org.lucci.madhoc.simulation.projection.Projection;
import org.lucci.util.assertion.Assertions;




public class Network implements Configurable
{
    public Map<String, NetworkingTechnology> networkTypes = new HashMap<String, NetworkingTechnology>(); 

    private Simulation simulation;

    private List<Station> stations = new Vector<Station>();
    private Collection<Connection> connections;

    // the applications running on top of the simulation
    private Map<Class, Monitor> simulationApplicationMap = new HashMap<Class, Monitor>();

    private Collection<Application> computerApplicationList; 

    // each station in the network have the same mobility
    // each mobile station will have its own instance of the mobility model 
    private NetworkEnvironment networkEnvironment;

    private Map<Class, Projection> projectionMap = new HashMap<Class, Projection>();

    private boolean simulateSynchronization;

    private boolean useDeliveryFailureMessages = true;


    public List<Station> getStations()
    {
        return stations;
    }
    public void setStations(List<Station> stations)
    {
        this.stations = stations;
    }

    public Map<Class, Monitor> getSimulationApplicationMap()
    {
        return simulationApplicationMap;
    }
    
    public void setSimulationApplicationMap(Map<Class, Monitor> simulationApplicationMap)
    {
        this.simulationApplicationMap = simulationApplicationMap;
    }
    
    public void setConnections(Collection<Connection> connections)
    {
        this.connections = connections;
    }
    
    public void setProjectionMap(Map<Class, Projection> projectionMap)
    {
        this.projectionMap = projectionMap;
    }

    public void deployApplication(Monitor monitor)
        throws Throwable
    {
        monitor.setNetwork(this);
        simulationApplicationMap.put(monitor.getClass(), monitor);
        
        if (monitor.getStationApplicationClass() != null)
        {
            monitor.configure();
            monitor.deploy();

            if (monitor.getInitializer() != null)
            {
                monitor.getInitializer().initialize();
            }
        }
    }

    public Map<Class, Monitor>  getMonitorMap()
    {
        return Collections.unmodifiableMap(simulationApplicationMap);
    }


    public Collection<Connection> findConnections()
    {
        Collection<Connection> connections = new HashSet<Connection>();
        
        for (Station station : getStations())
        {
            for (NetworkInterface ni : station.getNetworkingUnit().getNetworkInterfaces())
            {
                connections.addAll(ni.getConnections());
            }
        }
        
        return connections;
    }

    
    public Collection<Connection> getConnections()
    {
        if (this.connections == null)
        {
            this.connections = findConnections();
        }

        return this.connections;
    }

    public Collection<Connection> findConnections(NetworkingTechnology type)
    {
        Collection<Connection> connections = new Vector<Connection>(); 
        
        for (Connection connection : findConnections())
        {
            if (connection.getNetworkType() == type)
            {
                connections.add(connection);
            }
        }
        
        return connections;
    }


    public List<Station> findComputersAcceptableIn(Projection r)
    {
        List<Station> collection = new Vector<Station>();
        
        for (Station c : getStations())
        {
            if (r.acceptComputer(c))
            {
                collection.add(c);
            }
        }

        return collection;
    }

    public Station findComputer(int id)
    {
        for (Station c : getStations())
        {
            if (c.getIdentifier() == id)
            {
                return c;
            }
        }
        
        return null;
    }
    
    public NetworkEnvironment getNetworkEnvironment()
    {
        return networkEnvironment;
    }

    public void setNetworkEnvironment(NetworkEnvironment networkModel)
    {
        if (!getStations().isEmpty())
            throw new IllegalStateException("the network environment has to be defined before the stations are created, otherwise it has no effect");

        if (networkModel == null)
            throw new IllegalArgumentException();

        this.networkEnvironment = networkModel;
    }

//  public void updateConnections()
//  {
//      removeInvalidConnections();
//      findNewConnections();
//  }

    /*
     * A connection can be become invalid because the two stations are two
     * far away to one another, or because the wave propagation cannot
     * propagate because of environemental constraints.
     */
    public Collection<Connection> removeInvalidConnections()
    {
        Collection<Connection> invalidConnections = new HashSet<Connection>();
        // detect and remove lost connections
                
        for (Station station : getStations())
        {
            if (station.isSwitchedOn())
            {
                for (NetworkInterface ni : station.getNetworkingUnit().getNetworkInterfaces())
                {
                    for (Connection connection : ni.getConnections())
                    {
                        // if the connection has already been removed during the analysis of the other node
                        if (!invalidConnections.contains(connection))
                        {
                            if (connection.getNetworkType().isAutomatic())
                            {
                                // the devices are still in range
                                if (connection.getDistance() < connection.getGreatestDistancePossible())
                                {
                                    connection.setEnvironmentPerturbation(getNetworkEnvironment().getRadioPropagationModel().getEnvironmentPerturbation(connection));
                                
                                    // if the quality is worse than 0.001, we consider that the degradation is
                                    // too important and the connection is no more usable
                                    if (connection.getQuality() < 0.01)
                                    {
                                        invalidConnections.add(connection);
                                    }
                                }
                                else
                                {
                                    invalidConnections.add(connection);
                                }
                            }
                        }
                    }
                }
            }
            else
            {
                invalidConnections.addAll(station.getNetworkingUnit().getConnections());
            }
        }

        for (Connection c : invalidConnections)
        {
            c.remove();
        }
        
        return invalidConnections;
    }

    
    
    public Collection<Connection> createNewConnections()
    {
        Collection<Connection> newConnections = new Vector<Connection>();

        // ask each station to find its network neighborhood
        for (Station station : getStations())
        {
            if (station.isSwitchedOn())
            {
                // there are several separated networks (Wifi, Bluetooth...), each of them have to be scanned
                for (NetworkInterface networkAdapter : station.getNetworkingUnit().getNetworkInterfaces())
                {
                    if (networkAdapter.getNetworkingTechnology().isAutomatic())
                    {
                        for (NetworkInterface surroundingNetworkAdapter : getSurroundingCompliantNetworkInterfaces(networkAdapter))
                        {
                            // if both stations have not reached the maximum number of simultaneous connections, we can try to establish a new one
                            if (networkAdapter.getConnections().size() < networkAdapter.getNetworkingTechnology().getMaximumNumberOfConnectionsPossible()
                                    && surroundingNetworkAdapter.getConnections().size() < surroundingNetworkAdapter.getNetworkingTechnology().getMaximumNumberOfConnectionsPossible())
                            {
                                // if one of the nodes cannot sync because it already communicates with others, no connection is possible
                                if (!simulateSynchronization || networkAdapter.canSyncTo(surroundingNetworkAdapter))
                                {
                                    Connection connection = new Connection(networkAdapter, surroundingNetworkAdapter);

                                    if (connection.getDistance() > connection.getGreatestDistancePossible())
                                        throw new IllegalStateException();
                                    
                                    if (!networkAdapter.getConnections().contains(connection) && !surroundingNetworkAdapter.getConnections().contains(connection))
                                    {
                                        connection.setEnvironmentPerturbation(getNetworkEnvironment().getRadioPropagationModel().getEnvironmentPerturbation(connection));
                                        
                                        // if the signal is strong enough to transmit the data
                                        if (connection.getQuality() > 0.01)
                                        {
                                            networkAdapter.getConnections().add(connection);
                                            surroundingNetworkAdapter.getConnections().add(connection);
                                            connection.setCreationDate(getSimulation().getSimulatedTime());
                                            newConnections.add(connection);
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
        
        return newConnections;
    }

    


    public void createBidirectionalConnection(Station s1, Station s2, String type)
    {
        NetworkingTechnology nt = getNetworkTypes().get(type);
        
        if (nt == null)
            throw new IllegalStateException(type + " connections are not supported");

        NetworkInterface ni1 = s1.getNetworkingUnit().getNetworkInterface(nt);
        NetworkInterface ni2 = s2.getNetworkingUnit().getNetworkInterface(nt);
        
        if (ni1 == null || ni2 == null)
            throw new IllegalStateException("at least one of the two computers does not have a " + type + " adapter");

        addConnection(ni1, ni2);
    }


    
    
    public Collection<Connection> findUnidirectionalConnections()
    {
        Collection<Connection> connections = new Vector<Connection>();
        
        for (Connection c : getConnections())
        {
            if (!c.getNetworkInterface1().getConnections().contains(c) || !c.getNetworkInterface2().getConnections().contains(c))
            {
                connections.add(c);
            }
        }

        return connections;
    }
    
    public Connection addConnection(NetworkInterface ni1, NetworkInterface ni2)
    {
        Connection connection = new Connection(ni1, ni2);
        ni1.getConnections().add(connection);
        ni2.getConnections().add(connection);
        return connection;
    }

    
    private Collection<NetworkInterface> getSurroundingCompliantNetworkInterfaces(final NetworkInterface networkAdapter)
    {
        Collection<NetworkInterface> surroundingNetworkAdapters = new Vector<NetworkInterface>();
        Collection<Station> nodesInTheSurroundingCels = getNodesInTheSurroundingCels(networkAdapter);

        for (Station otherStation : nodesInTheSurroundingCels)
        {
            if (otherStation.isSwitchedOn())
            {
                // if we don't try to connect a station to itself
                if (otherStation != networkAdapter.getNetworkingUnit().getStation())
                {
                    // checks if the station found provides a compliant network adapter
                    NetworkInterface otherNetworkAdapter = otherStation.getNetworkingUnit().getNetworkInterface(networkAdapter.getNetworkingTechnology());

                    if (otherNetworkAdapter != null)
                    {
                        // ensure that the both stations are in the coverage circle of each other
                        double distance = networkAdapter.getNetworkingUnit().getStation().getLocation().getDistanceTo(otherStation.getLocation());

                        if (distance < networkAdapter.getCoverageRadius() && distance < otherNetworkAdapter.getCoverageRadius())
                        {
                            surroundingNetworkAdapters.add(otherNetworkAdapter);
                        }
                    }
                }
            }
        }

        return surroundingNetworkAdapters;
    }


    private Collection<Station> getNodesInTheSurroundingCels(final NetworkInterface networkAdapter)
    {
        Collection<Station> nodes = new Vector<Station>();
        Cel localCel = networkAdapter.getNetworkingUnit().getStation().getLocation().getCel();
        int numberOfCelsInRadius = (int) (networkAdapter.getCoverageRadius() / 10d) + 1;
        
        for (int ioffset = -numberOfCelsInRadius; ioffset < numberOfCelsInRadius; ++ioffset)
        {
            int i = localCel.getX() + ioffset;
            
            if (0 < i && i < getNetworkEnvironment().getGrid().getCels().length)
            {
                for (int joffset = -numberOfCelsInRadius; joffset < numberOfCelsInRadius; ++joffset)
                {
                    int j = localCel.getY() + joffset;

                    if (0 < j && j < getNetworkEnvironment().getGrid().getCels()[i].length)
                    {
                        nodes.addAll(getNetworkEnvironment().getGrid().getCels()[i][j].getStations());
                    }   
                }
            }
        }
        
        return nodes;
    }

    
    /**
     * Creates the devices population according to the given density.
     * The density is the number of inhabitants per square kilometer.
     * 90% are pedestrian
     * 9% are cars
     * 1% are trains
     */
    public void createComputers()
        throws Throwable
    {
        int computerId = 0;
        double squareKms = getNetworkEnvironment().getGrid().getEdgeLenght() * getNetworkEnvironment().getGrid().getEdgeLenght() / 1000000d;

        double laptopDensity = simulation.getConfiguration().getDouble("network_laptop_density");
        double laptopCount = laptopDensity * squareKms;
        
        for (int i = 0; i < laptopCount; ++i)
        {
            Station computer = new Station();
            computer.setType(ComputerType.LAPTOP);
            stations.add(computer);
        }

        double pagerDensity = simulation.getConfiguration().getDouble("network_pager_density");
        double pagerCount = pagerDensity * squareKms;
        
        for (int i = 0; i < pagerCount; ++i)
        {
            Station computer = new Station();
            computer.setType(ComputerType.PAGER);
            stations.add(computer);
        }

        double phoneDensity = simulation.getConfiguration().getDouble("network_phone_density");
        double phoneCount = phoneDensity * squareKms;
        
        for (int i = 0; i < phoneCount; ++i)
        {
            Station computer = new Station();
            computer.setType(ComputerType.MOBILE_PHONE);
            stations.add(computer);
        }

        double hotspotDensity = simulation.getConfiguration().getDouble("network_hotspot_density");
        double hotspotCount = hotspotDensity * squareKms;

        for (int i = 0; i < hotspotCount; ++i)
        {
            Station computer = new Station();
            computer.setType(ComputerType.HOTSPOT);
            stations.add(computer);
        }

        for (Station node : stations)
        {
            node.setIdentifier(computerId++);
            node.setNetwork(this);
            node.configure();
        }
    }
    


    
    
    public Collection getStationsWithMobilityMedium(Class mm)
    {
        Collection<Station> c = new Vector<Station>();
        Iterator stationsIterator = getStations().iterator();
        
        while (stationsIterator.hasNext())
        {
            Station station = (Station) stationsIterator.next();
            
            if (station.getMobilityModel().getMobilityMedium().getClass() == mm)
            {
                c.add(station);
            }
        }

        return c;
    }

    public Collection<Application> getStationApplications()
    {
        if (computerApplicationList == null)
        {
            computerApplicationList = new HashSet<Application>();
            
            for (Station station : getStations())
            {
                computerApplicationList.addAll(station.getApplicationMap().getInverseRelation().getKeys()); 
            }
            
            computerApplicationList = Collections.unmodifiableCollection(computerApplicationList);
        }
        
        return computerApplicationList;
    }


    public void resetIterationScopedValues()
    {
        computerApplicationList = null;
        
        for (Station station : getStations())
        {
            for (NetworkInterface ni : station.getNetworkingUnit().getNetworkInterfaces())
            {
                for (Connection connection : ni.getConnections())
                {
                    connection.resetIterationScopedValues();
                }
            }
        }

        // the set of connections will change
        this.connections = null;
        
        for (Station s : getStations())
        {
            s.getNetworkingUnit().resetIterationScopedValues();
        }
    }

    class MessageTransfer
    {
        public Message message;
        public Station recipient;
        public Connection connection;
    }
    
    public void flushOutgoingMessageQueues()
    {
        // remove the messages for which the recipient stations are no more reachable
        for (Station station : getStations())
        {
            Iterator<Message> i = station.getNetworkingUnit().getOutgoingMessageQueue().internalList().iterator();
            
            while (i.hasNext())
            {
                Message message = i.next();
                Collection<Station> recipientStations = message.findRecipientStations();
                recipientStations.retainAll(station.getNetworkingUnit().getNeighborhood());
                
                if (recipientStations.isEmpty())
                {
                    i.remove();
                }
            }
        }
        
        List<MessageTransfer> messageTransfers = new Vector<MessageTransfer>();
        
        for (Station station : getStations())
        {
            Collection<Message> expiredMessages = new HashSet<Message>();
            Collection<Message> completedMessages = new HashSet<Message>();

            for (Message message : (List<Message>) station.getNetworkingUnit().getOutgoingMessageQueue().internalList())
            {
                Assertions.ensure(message.getMessageInformation() != null, "no message information");
                
                if (message.getMessageInformation().getCreationDate() < 0)
                {
                    message.getMessageInformation().setCreationDate(getSimulation().getSimulatedTime());
                }
                
                double messageAge = getSimulation().getSimulatedTime() - message.getMessageInformation().getCreationDate();
                
                if (messageAge > message.getMessageInformation().getValidity())
                {
                    expiredMessages.add(message);
                }
                else
                {
                    Collection<Station> recipientStations = message.findRecipientStations();
                    List<MessageTransfer> messageTransfersForThisMessage = new Vector<MessageTransfer>();
                    
                    for (Station recipientNode : recipientStations)
                    {
                        // if the message has not been delivered to this node, we'll try
                        // to use all the connections available to transmit it
                        if (message.getNumberOfBytesDeliveredTo(recipientNode) < message.getSizeInBytes())
                        {
                            Collection<Connection> connectionsToRecipient = message.getSourceStationApplication().getComputer().getNetworkingUnit().getConnectionsTo(recipientNode);

                            for (Connection connection : connectionsToRecipient)
                            {
                                MessageTransfer entry = new MessageTransfer();
                                entry.message = message;
                                entry.recipient = recipientNode;
                                entry.connection = connection;
                                messageTransfersForThisMessage.add(entry);
                            }
                        }
                    }

                    if (messageTransfersForThisMessage.isEmpty())
                    {
                        completedMessages.add(message);
                    }
                    
                    messageTransfers.addAll(messageTransfersForThisMessage);
                }
            }
            
            // messages whose transfer has completed must be removed from the output buffer
            for (Message message : completedMessages)
            {
                message.getSourceStationApplication().getComputer().getNetworkingUnit().getOutgoingMessageQueue().remove(message);
            }
            
            // messages that have expired should go back to their sender, just like e-mails do
            if (this.useDeliveryFailureMessages )
            {
                for (Message expiredMessage : expiredMessages)
                {
                    Message deliveryFailureMessage = new Message();
                    deliveryFailureMessage.setContent(expiredMessage);
                    // back to sender
                    deliveryFailureMessage.getRecipientApplications().add(expiredMessage.getSourceStationApplication());
                    deliveryFailureMessage.setType(Message.DELIVERY_FAILURE);

                    try
                    {
                        station.getNetworkingUnit().getIncomingMessageQueue().add(deliveryFailureMessage);
                        station.getNetworkingUnit().getOutgoingMessageQueue().remove(expiredMessage);
                    }
                    catch (IOException e)
                    {
                        e.printStackTrace();
                    }
                }
            }
        }

        processMessageTransfers(messageTransfers);
    }
    
    private void processMessageTransfers(Collection<MessageTransfer> messageTransfers)
    {
        while (!messageTransfers.isEmpty())
        {
            Iterator messageTransferIterator = messageTransfers.iterator();
            
            while (messageTransferIterator.hasNext())
            {
                MessageTransfer messageTransfer = (MessageTransfer) messageTransferIterator.next();

                // this transfer is no more useful, it has been completed by another one
                if (messageTransfer.message.getNumberOfBytesDeliveredTo(messageTransfer.recipient) == messageTransfer.message.getSizeInBytes())
                {
                    messageTransferIterator.remove();
                }
                else
                {
                    int numberOfBytesTransfered = pursue(messageTransfer);
                    messageTransfer.connection.getNetworkType().charge(messageTransfer.message.getSourceStationApplication().getComputer(), numberOfBytesTransfered); 
                    
                    // transmission stalled
                    if (numberOfBytesTransfered == 0)
                    {
                        // no need to try again, it wouldn't help
                        // it will be retried at the next iteration
                        messageTransferIterator.remove();
                    } 
                    else
                    {
                        // if the message has been been delivered entirely
                        if (messageTransfer.message.getNumberOfBytesDeliveredTo(messageTransfer.recipient) == messageTransfer.message.getSizeInBytes())
                        {
                            // if there were no collisions
                            if (messageTransfer.message.getNumberOfCollisions() == 0)
                            {
                                try
                                {
                                    // and made available to the recipient node
                                    messageTransfer.recipient.getNetworkingUnit().getIncomingMessageQueue().add((Message) messageTransfer.message.clone());
                                }
                                catch (IOException ex)
                                {
                                    throw new IllegalStateException();
                                }
                            }
                            
                            messageTransferIterator.remove();
                        }
                    }
                }
            }
        }
    }

    /**
     * Returns the number of bytes sent
     * @param msg
     * @return
     */
    public int pursue(MessageTransfer messageTransfer)
    {
        // gets the bandwidth in b/s
        double availableBandwidth = messageTransfer.connection.getAvailableBandwith();

        double availableInputBufferSize = messageTransfer.recipient.getNetworkingUnit().getIncomingMessageQueue().getAvailableSize();

        // the speed of the transmission is constrained by the weakest component
        int amountOfDataToSendNow = (int) Math.min(availableBandwidth, availableInputBufferSize);

        // the size of data transmitted cannot exceed the size of a standard packet,
        // in order not to take all the bandwidth available with this message
        // maybe there are some other important message to transmit
        int packetSize = messageTransfer.connection.getNetworkType().getPacketSize();
        amountOfDataToSendNow = Math.min(amountOfDataToSendNow, packetSize);

        // now have we have the upper value for the amount of data to transmit
        // let's look how many bytes we will effectively transmit throught the connection 
        int remainingDataSize = messageTransfer.message.getSizeInBytes() - messageTransfer.message.getNumberOfBytesDeliveredTo(messageTransfer.recipient);

        if (remainingDataSize == 0)
            throw new IllegalStateException();

        amountOfDataToSendNow = Math.min(amountOfDataToSendNow, remainingDataSize);
                                                                                
        if (amountOfDataToSendNow > 0)
        {
            // the transmission reduces the bandwidth available to the other messages on this connection
            messageTransfer.connection.loadBandwith(amountOfDataToSendNow);

            double totalNumberOfBytesOnTheConnections = 0;
            
            // but also on the other connections of the same network interface
            Collection<Connection> sibblingConnections = messageTransfer.message.getSourceStationApplication().getComputer().getNetworkingUnit().getNetworkInterface(messageTransfer.connection.getNetworkType()).getConnections();

            for (Connection sibblingConnection : sibblingConnections)
            {
                totalNumberOfBytesOnTheConnections += sibblingConnection.getUsedBandwith();
                
                // if the connection is slower, we need to pay attention to this
                sibblingConnection.loadBandwith((int) Math.min(amountOfDataToSendNow / getSimulation().getResolution(), sibblingConnection.getAvailableBandwith()));
            }


//          if (Math.random() < totalNumberOfBytesOnTheConnections / )
//          {
//              messageTransfer.message.setNumberOfCollisions(messageTransfer.message.getNumberOfCollisions() + 1);
//          }

            
            remainingDataSize -= amountOfDataToSendNow;
            messageTransfer.message.addNumberOfBytesDeliveredTo(messageTransfer.recipient, amountOfDataToSendNow);
        }
        
        return amountOfDataToSendNow;
    }

//  private double getProbabilityOfCollision(MessageTransfer t)
//  {
//      NetworkInterface networkIterface = t.message.getSourceStationApplication().getComputer().getNetworkingUnit().getNetworkInterface(t.connection.getNetworkType());
//      Collection connections = networkIterface.getConnections();
//      Iterator connectionIterator = connections.iterator();
//      
//      while (connectionIterator.hasNext())
//      {
//          Connection connection  = (Connection) connectionIterator.next();
//          
//          connection.getSibbling(null)
//      }
//  }
    
    /* (non-Javadoc)
     * @see org.lucci.madhoc.Configurable#configure(org.lucci.madhoc.Configuration)
     */
    public void configure()
        throws Throwable
    {
        this.useDeliveryFailureMessages = getSimulation().getConfiguration().getBoolean("use_delivery_failure_messages");
        
        
        this.simulateSynchronization = getSimulation().getConfiguration().getBoolean("simulate_synchronization");
        
        Collection<String> networkTechnologyNames = getSimulation().getConfiguration().getStrings("network_technologies_available");
        
        for (String networkTechnologyName : networkTechnologyNames)
        {
            NetworkingTechnology type = new NetworkingTechnology();
            type.setName(networkTechnologyName);
            type.setNetwork(this);
            type.configure();
            this.networkTypes.put(type.getName(), type);
        }

        
        for (Projection projection : (List<Projection>) getSimulation().getConfiguration().getInstantiatedClasses("network_projections"))
        {
            projection.setSourceNetwork(this);
            projection.configure();
            projectionMap.put(projection.getClass(), projection);
        }
        

        // define a natural environment for the network
        // this environment will have a great impact on the structure of the network
        // for instance, the city environment will incorporate walls that will prevent the
        // radio waves from propagating
        // here we define a 1 square kilometer (1 million square meter)
        // open area (which could be a field)
        NetworkEnvironment environment = (NetworkEnvironment) simulation.getConfiguration().getInstantiatedClass("network_environment");
        environment.setNetwork(this);
        setNetworkEnvironment(environment);
        getNetworkEnvironment().configure();

        createComputers();



        
        // the stations have been located randomly on the environment
        // it might be interested to let them run for a while in order
        // to stabilize the network graph
        int iterationCount = simulation.getConfiguration().getInteger("mobility_prerun_iteration_count");
        double timeStep = simulation.getConfiguration().getDouble("mobility_prerun_time_step");
        getNetworkEnvironment().initializeNodesLocation(getStations(), iterationCount, timeStep);

        // once we consider the location of the station has been initialized
        // we establish the connections that define the network itself
        createNewConnections();
    }


    public Simulation getSimulation()
    {
        return simulation;
    }
    
    public Map<Class, Projection> getProjectionMap()
    {
        return projectionMap;
    }


    
    public void setSimulation(Simulation simulation)
    {
        if (this.simulation != null)
            throw new IllegalStateException("simulation already defined");

        this.simulation = simulation;
    }
    public Map<String, NetworkingTechnology> getNetworkTypes()
    {
        return networkTypes;
    }


    

}