package org.lucci.madhoc.network.net;

import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;

import org.lucci.madhoc.network.Application;
import org.lucci.madhoc.network.ComputerUnit;
import org.lucci.madhoc.network.Connection;
import org.lucci.madhoc.network.Station;
import org.lucci.madhoc.network.util.Graph;
import org.lucci.madhoc.simulation.Simulation;

/*
 * Created on Oct 20, 2004
 */

/**
 * @author luc.hogie
 */
public class NetworkingUnit extends ComputerUnit
{
    private static int instanceCount = 0; 

    private int id = instanceCount++;
    private MessageBuffer incomingMessageQueue = new MessageBuffer();

    private MessageBuffer outgoingMessageQueue = new MessageBuffer();
    private Collection<NetworkInterface> networkAdapters = new Vector<NetworkInterface>();



    private Collection<Station> neighborStations;
    private Collection<Connection> connections;

    
    
    
    
    public void configure()
        throws Throwable
    {
        Simulation simulation = getStation().getNetwork().getSimulation();
        
        // gets the size 
        int size = simulation.getConfiguration().getInteger("communicating_device_outgoing_message_queue_size");
        getOutgoingMessageQueue().setMaximumSize((int) (size * simulation.getResolution()));
        
        int size2 = simulation.getConfiguration().getInteger("computer_application_incoming_message_queue_size");
        getIncomingMessageQueue().setMaximumSize((int) (size2 * simulation.getResolution()));


        while (getNetworkInterfaces().isEmpty())
        {
            Iterator networkTechnologyIterator = getStation().getNetwork().getNetworkTypes().values().iterator();
            
            while (networkTechnologyIterator.hasNext())
            {
                NetworkingTechnology networkTechnology = (NetworkingTechnology) networkTechnologyIterator.next(); 

                if (getStation().getNetwork().getSimulation().getRandomNumberGenerator().getRandom().nextDouble()
                        < simulation.getConfiguration().getDouble("network_" + getStation().getType().getName() + "_" + networkTechnology.getName() + "_probability"))
                {
                    NetworkInterface networkAdapter = new NetworkInterface();
                    networkAdapter.setNetworkingUnit(this);
                    networkAdapter.setNetworkingTechnology(networkTechnology);
                    getNetworkInterfaces().add(networkAdapter);
                }
            }
        }
    }

    
    public Collection<Station> getNeighborhood()
    {
        if ( neighborStations == null )
        {
            neighborStations = new HashSet<Station>();
            
            for (NetworkInterface networkAdapter : getNetworkInterfaces())
            {
                for (NetworkInterface neighborInterface : networkAdapter.getNeighborhood())
                {
                    neighborStations.add(neighborInterface.getNetworkingUnit().getStation());
                }
            }
        }

        return Collections.unmodifiableCollection(neighborStations);
    }
    
    public Collection<Application> getApplicationsInTheNeighborhood(Class clazz)
    {
        Collection<Application> apps = new HashSet<Application>();
        Iterator i = getNeighborhood().iterator();
        
        while (i.hasNext())
        {
            Station s = (Station) i.next();
            Application app = (Application) s.getApplicationMap().getValue(clazz);
            
            if (app != null)
            {
                apps.add(app);
            }
        }
        
        return apps;
    }


    public MessageBuffer getOutgoingMessageQueue()
    {
        return outgoingMessageQueue;
    }



    /**
     * @return Returns the networkAdapters.
     */
    public Collection<NetworkInterface> getNetworkInterfaces()
    {
        return networkAdapters;
    }

    /**
     * @param class1
     * @return
     */
    public NetworkInterface getNetworkInterface(NetworkingTechnology networkType)
    {
        Iterator networkAdapterIterator = getNetworkInterfaces().iterator();
        
        while (networkAdapterIterator.hasNext())
        {
            NetworkInterface networkAdapter = (NetworkInterface) networkAdapterIterator.next();
            
            if (networkAdapter.getNetworkingTechnology() == networkType)
            {
                return networkAdapter;
            }
        }
        
        return null;
    }


    public int hashCode()
    {
        return id;
    }

    

    public Collection<Connection> getConnections()
    {
        if (connections == null)
        {
            connections = new Vector<Connection>();

            // we harvest the network adapters around
            Iterator networkAdapterIterator = getNetworkInterfaces().iterator();
            
            while (networkAdapterIterator.hasNext())
            {
                NetworkInterface networkAdapter = (NetworkInterface) networkAdapterIterator.next();
                connections.addAll(networkAdapter.getConnections());
            }

            connections = Collections.unmodifiableCollection(connections);
        }
        
        return connections;
    }

    public Collection<Connection> getConnectionsTo(Station station)
    {
        Collection<Connection> set = new Vector<Connection>();
        Iterator networkAdapterIterator = getNetworkInterfaces().iterator();
        
        while (networkAdapterIterator.hasNext())
        {
            NetworkInterface networkAdapter = (NetworkInterface) networkAdapterIterator.next();
            Iterator connectionIterator = networkAdapter.getConnections().iterator();
            
            while (connectionIterator.hasNext())
            {
                Connection c = (Connection) connectionIterator.next();
                
                if (c.getSibbling(networkAdapter).getNetworkingUnit().getStation() == station)
                {
                    set.add(c);
                }
            }
        }

        return set;
    }

    public Connection getBestConnectionTo(Station station)
    {
        Collection<Connection> set = getConnectionsTo(station);
        
        if (set.isEmpty())
        {
            return null;
        }
        else
        {
            Iterator i = getConnectionsTo(station).iterator();
            Connection best = (Connection) set.iterator().next();

            while (i.hasNext())
            {
                Connection c = (Connection) i.next();
                
                if (c.getAvailableBandwith() > best.getAvailableBandwith())
                {
                    best = c;
                }
            }
            
            return best;
        }
    }


    // the neighborhood at the previous iteration
    // initially the collection is null
    // the first invocation then do not return the 
    // set of all neighbors but an empty set,
    // otherwise, at startup, stations think they have a new neighborhood
    private Collection<Connection> previousConnections = null;
    private Collection<Connection> newConnections;

    public Collection<Connection> getNewConnections()
    {
        if (newConnections == null)
        {
            if (previousConnections == null)
            {
                previousConnections = new HashSet<Connection>(getConnections());
                newConnections = new HashSet<Connection>();
            }
            else
            {
                Collection<Connection> currentConnections = getConnections();
                newConnections = new HashSet<Connection>(currentConnections);
                newConnections.removeAll(previousConnections);
                previousConnections = currentConnections;
            }
        }

        return newConnections;
    }





    public Collection<Station> getNeighborhoodAtHop(int hop)
    {
        if (hop < 0)
            throw new IllegalArgumentException();

        List<Collection<Station>> hops = Graph.getHops(this, hop);

        if (hop < hops.size())
        {
            return (Collection<Station>) hops.get(hop);
        }
        else
        {
            return new Vector<Station>();
        }
    }



    

    /**
     * 
     */
    public void resetIterationScopedValues()
    {
        neighborStations = null;
        connections = null;
        newConnections = null;
    }

    
    public MessageBuffer getIncomingMessageQueue()
    {
        return incomingMessageQueue;
    }
}