#include "main.h"

Graph::Graph(int size, int k, int maxPeers, mt19937& rng,  const Latency& latency) :
	generator(mt19937(rng())), size(size), k(k), maxPeers(maxPeers), latency(latency),
	distrib(uniform_int_distribution<int>(0, size-1))
{
	adjacency = new unordered_set<int>[size];
	generated = new unordered_set<int>[size];

	for(int i=0; i<size; i++)
		SaturateNode(i);
}

Graph::Graph(const Graph& g) :
	generator(g.generator), size(g.size), k(g.k), maxPeers(g.maxPeers), latency(latency),
	distrib(g.distrib)
{
	adjacency = new unordered_set<int>[size];
	generated = new unordered_set<int>[size];

	for(int i=0; i<size; i++)
	{
		adjacency[i] = unordered_set<int>(g.adjacency[i]);
		generated[i] = unordered_set<int>(g.generated[i]);
	}
}

void Graph::SaturateNode(int node)
{
	while(generated[node].size() < k && AddEdge(node)) { }
}

bool Graph::AddEdge(int from)
{
	int to;
	for(int i=0; i<50; i++)
	{
		to = distrib(generator);

		if(	to != from
			&& latency.values[from][to] > 0
			&& adjacency[from].count(to) == 0
			&& adjacency[to].size() + k < maxPeers + generated[to].size())
		{
			generated[from].insert(to);
			adjacency[from].insert(to);
			adjacency[to].insert(from);
			return true;
		}
	}

	//cout << "Warning : a link could not be established from " << from << endl;
	return false;
}

void Graph::RemoveEdge(int from, int to)
{
	generated[from].erase(to);
	adjacency[from].erase(to);
	adjacency[to].erase(from);
}

void Graph::GetRoutesFrom(int from,  int* nRoutes, int* prevs, int* distances)
{
	// init vars
    stack<int> order;

    for(int i=0; i<size; i++)
    {
        distances[i] = -1;
        nRoutes[i] = 1;
    }
    distances[from] = 0;

    priority_queue<pair<int, int>> remainingNodes;
    remainingNodes.push(pair<int, int>(-0, from));

    // Get the order
    while(!remainingNodes.empty())
    {
    	pair<int, int> p = remainingNodes.top();
        int node = p.second;
        int d = -p.first;
        remainingNodes.pop();

        if(d == distances[node])
        {
	        order.push(node);
	        for(int neighbor : adjacency[node])
	        {
	        	int neighborDist = d + latency.values[neighbor][node];

	            if(distances[neighbor] == -1 || distances[neighbor] > neighborDist)
	            {
	                distances[neighbor] = neighborDist;
	                prevs[neighbor] = node;
	                remainingNodes.push(pair<int, int>(-neighborDist, neighbor));
	            }
	        }
	    }
    }

    // get the BC
    // The error is here
    while(!order.empty())
    {
        int node = order.top();
        order.pop();
        if(distances[node] != -1 && node != from)
        	nRoutes[prevs[node]] += nRoutes[node];
    }
}


int Graph::UpdateLowRoutes(double& avgDistance, double unreachable, double* arityDistrib,
		double* bcArity, int nRefresh, int round)
{
	int nUpdated = 0;
	routesResult results[size];
	double bc[size];
	for(int i=0; i<size; i++)
		bc[i] = 0;

	avgDistance = 0;
	double avgDistanceWeight = 0;
	unreachable = 0;
	for(int i=0; i<=maxPeers; i++)
	{
		bcArity[i] = 0;
		arityDistrib[i] = 0;
	}

	for(int i=0; i<size; i++)
	{
    	// Compute the routes
        int nRoutes[size], prevs[size], distances[size];
        GetRoutesFrom(i, nRoutes, prevs, distances);
        for(int j=0; j<size; j++)
        	bc[j] += nRoutes[j];

        // Get the values
        routesResult r;
        
        for(int k=0; k<nRefresh; k++)
        {
        	int mini = -1;
			for(int j : generated[i])
				if(mini == -1 || nRoutes[mini] > nRoutes[j])
					mini = j;

			if(mini != -1)
				r.toDelete.push(mini);
		}

		r.arity = adjacency[i].size();

		r.avgDistance = 0;
		r.unreachable = 0;
		for(int j=0; j<size; j++)
		{
			if(distances[i] >= 0)
				r.avgDistance += distances[j];
			else
				r.unreachable++;
		}

		r.avgDistance /= (double)(size - r.unreachable);

		results[i] = r;
	}
	
	for(int i = 0; i<size; i++)
	{
		routesResult r = results[i];
		if((adjacency[i].size() > 16 &&  adjacency[i].size() < 26) || round % 4 == 0)
		{
			nUpdated++;
			while(!r.toDelete.empty())
			{
				RemoveEdge(i, r.toDelete.top());
				r.toDelete.pop();
			}
		}

		SaturateNode(i);

		avgDistance += r.avgDistance*(size-r.unreachable);
		avgDistanceWeight += size-r.unreachable;
		unreachable += r.unreachable;
		arityDistrib[adjacency[i].size()]++;
		bcArity[adjacency[i].size()] += bc[i] - 2*size + 1;
	}

	avgDistance /= avgDistanceWeight;

	for(int i=0; i<=maxPeers; i++)
	{
		bcArity[i] = arityDistrib[i]>0 ? bcArity[i] / arityDistrib[i]:0;
		arityDistrib[i] /= size;
	}

	return nUpdated;
}

int Graph::CountUnreachableFrom(int node)
{
    bool accessibility[size];
    for(int i=0; i<size; i++)
        accessibility[i] = false;
    accessibility[node] = true;
    int unreachable = size;

    queue<int> toVisit;
    toVisit.push(node);
    while(!toVisit.empty())
    {
        int n = toVisit.front();
        for(int i : adjacency[n])
        {
            if(!accessibility[i])
            {
                toVisit.push(i);
                accessibility[i] = true;
            }
        }

        unreachable--;
        toVisit.pop();
    }

    return unreachable;
}

double Graph::GetUnAvalaibility()
{
	double moy = 0;
	for(int i=0; i<size; i++)
		moy += CountUnreachableFrom(i);
	return moy / (size*size);
}

void Graph::KillMachines(float proportion)
{
    size = proportion*size;
	distrib = uniform_int_distribution<int>(0, size - 1);

    for(int i=0; i<size; i++)
    {
    	vector<int> toBeRemoved;
    	for(int j : adjacency[i])
    		if(j >= size)
    			toBeRemoved.push_back(j);

        for(int j : toBeRemoved)
        {
        	generated[i].erase(j);
        	adjacency[i].erase(j);
        }
    }
}