/*
 * Created on Jan 29, 2004
 */
package org.lucci.madhoc.network;

import org.lucci.madhoc.network.net.NetworkInterface;
import org.lucci.madhoc.network.net.NetworkingTechnology;


public class Connection
{
    private NetworkInterface d1;
    private NetworkInterface d2;
    private int usedBandwith = 0;
    private double creationDate = -1;
    private double suppressionDate = -1;

    // a decimal number between 0 and 1
    // 1 means that the environment has no impact on the connection
    // and 0 means that the environment prevents the connection
    private double environmentPerturbation = 0;
    
    public Connection(NetworkInterface d1, NetworkInterface d2)
    {
        if (!d1.getNetworkingTechnology().isCompliantWith(d2.getNetworkingTechnology()))
            throw new IllegalArgumentException("network interfaces do not use the same technology, then hence cannot establish a connection to each other");
            
        this.d1 = d1;
        this.d2 = d2;
    }

    public NetworkInterface getNetworkInterface1()
    {
        return d1;
    }

    public NetworkInterface getNetworkInterface2()
    {
        return d2;
    }

    /*
     * 2 connections a->b and b->a must have the same hashcode!!!!!!!!!
     * Otherwise 2 connections will be created instead of 1
     * @see java.lang.Object#hashCode()
     */
    public int hashCode()
    {
        return getIdentifier().hashCode();
    }

    public String getIdentifier()
    {
        int ni1 = getNetworkInterface1().hashCode(); 
        int ni2 = getNetworkInterface2().hashCode(); 
        return Math.min(ni1, ni2) + ":" + Math.max(ni1, ni2) + ":" + getNetworkType().getName();
    }
    
    public boolean equals(Object o)
    {
        return o instanceof Connection && o.hashCode() == hashCode();
    }

    public NetworkInterface getSibbling(NetworkInterface d)
    {
        if ( d == d1 )
        {
            return d2;
        }
        else if ( d == d2 )
        {
            return d1;
        }
        else
        {
            throw new IllegalArgumentException("station not associated to this connection");
        }
    }
    
    public double getDistance()
    {
        return getNetworkInterface1().getNetworkingUnit().getStation().getLocation().getDistanceTo(getNetworkInterface2().getNetworkingUnit().getStation().getLocation());
    }
    
    public double getGreatestDistancePossible()
    {
        return Math.min(getNetworkInterface1().getCoverageRadius(), getNetworkInterface2().getCoverageRadius());
    }
    
    public double getQuality()
    {
//      A = 92,4 + 20 log(f) + 20 log(D)
//
//      A => atténuation en espace libre (en dB)
//      f => fréquence de travail (en GHz)
//      D => distance entre émetteur et récepteur (en Km)



        double distance = getDistance();
        double maxDistance = getGreatestDistancePossible(); 

//      if (distance > maxDistance)
//      {
//          throw new IllegalStateException(distance + " > " + maxDistance);
//      }

        // defines the quality function of the distance between the 2 hosts
        double quality = (maxDistance - distance) / maxDistance; 

        // now apply the environmental constraints
        quality *= 1 - getEnvironmentPerturbation();

        return quality;
    }

    public int getUsedBandwith()
    {
        return this.usedBandwith;
    }


    public void loadBandwith(int i)
    {
        if ( i < 0 )
            throw new IllegalArgumentException("load cannot be negative");
            
        if (i > getAvailableBandwith())
            throw new IllegalArgumentException("load too high: " + i +  " > " + getAvailableBandwith());
        
        usedBandwith += i;
    }

    
    public int getMaximumBandwidth()
    {
        double resolution = getNetworkInterface1().getNetworkingUnit().getStation().getNetwork().getSimulation().getResolution();
        return (int) (Math.min(getNetworkInterface1().getNetworkingTechnology().getMaximumBandwith(), getNetworkInterface2().getNetworkingTechnology().getMaximumBandwith()) * getQuality() * resolution);
    }

    public int getAvailableBandwith()
    {
//        System.out.println(getNetworkInterface1().getNetworkingUnit().getStation().getNetwork().getSimulation().getIteration() + " : "  + (getMaximumBandwidth() - getUsedBandwith()));
        return getMaximumBandwidth() - getUsedBandwith();
    }
    
    public NetworkingTechnology getNetworkType()
    {
        if (getNetworkInterface1().getNetworkingTechnology() == getNetworkInterface2().getNetworkingTechnology())
        {
            return getNetworkInterface1().getNetworkingTechnology();
        }
        else
        {
            throw new IllegalStateException();
        }
    }

    public void remove()
    {
        getNetworkInterface1().getConnections().remove(this);
        getNetworkInterface2().getConnections().remove(this);
        setSuppressionDate(getNetworkInterface1().getNetworkingUnit().getStation().getNetwork().getSimulation().getSimulatedTime());
    }
    
    
    /**
     * 
     */
    public void resetIterationScopedValues()
    {
        usedBandwith = 0;
    }

    public double getEnvironmentPerturbation()
    {
        return environmentPerturbation;
    }
    public void setEnvironmentPerturbation(double p)
    {
        if (p < 0)
            throw new IllegalArgumentException();
        
        if (p > 1)
            throw new IllegalArgumentException();
        
        this.environmentPerturbation = p;
    }

    public double getCreationDate()
    {
        return creationDate;
    }

    public void setCreationDate(double creationDate)
    {
        if (this.creationDate != -1)
                throw new IllegalStateException("connection creation already timestamped");

        this.creationDate = creationDate;
    }


    public double getSuppressionDate()
    {
        return suppressionDate;
    }
    public void setSuppressionDate(double suppressionDate)
    {
        if (this.suppressionDate != -1)
            throw new IllegalStateException("connection suppression already timestamped");

        this.suppressionDate = suppressionDate;
    }
    
    public double getCollisionProbability()
    {
        double numberOfConcurentConnections = getNetworkInterface1().getConnections().size() + getNetworkInterface2().getConnections().size() - 1;
        // this function is stupid, let's change it
        return numberOfConcurentConnections / 100;
    }
}