#include "defs.h"

using namespace std;

char filename[FNAME_LEN];

struct sigma_t
{
    weight_t in, tot;
	
	sigma_t()
	{
		in = 0;
		tot = 0;
	}
};

struct tmp_struct
{
	vertex_id_t v;
	vertex_id_t c;
	weight_t w;
};

typedef map<vertex_id_t, sigma_t> sigma_map_t;
typedef map<vertex_id_t, weight_t> map_edges_t;
typedef map<vertex_id_t, map_edges_t> map_graph_t;

const double eps = 1e-4;

template<class T>
void free_buf(T **buf, int cnt)
{
	for (int i = 0; i < cnt; i++) {
		delete[] buf[i];
	}
	
	delete[] buf;
}

void get_out_component_id(vertex_id_t n, set<vertex_id_t> &out_components, map<vertex_id_t, vertex_id_t> &out_component_id, const vector<vertex_id_t> &component_id)
{
	int size, rank;
	MPI_Comm_size(MPI_COMM_WORLD, &size);
	MPI_Comm_rank(MPI_COMM_WORLD, &rank);
	int *send_cnt = new int[size];
    int *recv_cnt = new int[size];
    memset(send_cnt, 0, sizeof(int) * size);
    for (set<vertex_id_t>::iterator i = out_components.begin(); i != out_components.end(); i++) {
        send_cnt[VERTEX_OWNER(*i, n, size)]++;
    }
	
    MPI_Alltoall(send_cnt, 1, MPI_INT, recv_cnt, 1, MPI_INT, MPI_COMM_WORLD);
	
    int **recv_buf = new int *[size];
    for (int i = 0; i < size; i++) {
        recv_buf[i] = new int[recv_cnt[i]];
    }
	
    MPI_Request *requests = new MPI_Request[size];
    for (int i = 0; i < size; i++) {
        MPI_Irecv(recv_buf[i], recv_cnt[i], MPI_INT, i, 0, MPI_COMM_WORLD, &requests[i]);
    }
	
    MPI_Barrier(MPI_COMM_WORLD);
	
    int **send_buf = new int *[size];
    for (int i = 0; i < size; i++) {
        send_buf[i] = new int[send_cnt[i]];
    }
    
    memset(send_cnt, 0, sizeof(int) * size);
    for (set<vertex_id_t>::iterator i = out_components.begin(); i != out_components.end(); i++) {
        int owner = VERTEX_OWNER(*i, n, size);
        send_buf[owner][send_cnt[owner]++] = *i;
    }
    
    for (int i = 0; i < size; i++) {
        MPI_Send(send_buf[i], send_cnt[i], MPI_INT, i, 0, MPI_COMM_WORLD);
    }
    
    MPI_Waitall(size, requests, MPI_STATUSES_IGNORE);
	
    int **send_buf2 = new int *[size];
    for (int i = 0; i < size; i++) {
        send_buf2[i] = new int[recv_cnt[i]];
    }
    
    int **recv_buf2 = new int *[size];
    for (int i = 0; i < size; i++) {
        recv_buf2[i] = new int[send_cnt[i]];
    }
    
    for (int i = 0; i < size; i++) {
        MPI_Irecv(recv_buf2[i], send_cnt[i], MPI_INT, i, MPI_ANY_TAG, MPI_COMM_WORLD, &requests[i]);
    }
	
    MPI_Barrier(MPI_COMM_WORLD);
	
	memset(send_cnt, 0, sizeof(int) * size);
    for (int i = 0; i < size; i++) {
        for (int j = 0; j < recv_cnt[i]; j++) {
            send_buf2[i][send_cnt[i]++] = component_id[VERTEX_LOCAL(recv_buf[i][j], n, size, rank)];
        }
    }
    
    for (int i = 0; i < size; i++) {
        MPI_Send(send_buf2[i], send_cnt[i], MPI_INT, i, 0, MPI_COMM_WORLD);
    }
    
    MPI_Waitall(size, requests, MPI_STATUSES_IGNORE);
	
    memset(send_cnt, 0, sizeof(int) * size);
    for (set<vertex_id_t>::iterator i = out_components.begin(); i != out_components.end(); i++) {
        int owner = VERTEX_OWNER(*i, n, size);
        vertex_id_t id = recv_buf2[owner][send_cnt[owner]++];
        out_component_id[*i] = id;
    }
	
	delete[] requests;
    delete[] send_cnt;
    delete[] recv_cnt;
    
	free_buf(send_buf, size);
    free_buf(send_buf2, size);
    free_buf(recv_buf, size);
    free_buf(recv_buf2, size);
}

double modularity(graph_t &graph, const vector<vertex_id_t> &component_id)
{
	vertex_id_t n = graph.n;
	vertex_id_t local_n = graph.local_n;
	int size, rank;
    MPI_Comm_size(MPI_COMM_WORLD, &size);
    MPI_Comm_rank(MPI_COMM_WORLD, &rank);
    map<vertex_id_t, vertex_id_t> out_component_id;
    set<vertex_id_t> out_components;
    sigma_map_t local_sigma;
    sigma_map_t sigma;
    weight_t local_m = 0;
    for (vertex_id_t i = 0; i < local_n; i++) {
        for (edge_id_t e = graph.rowsIndices[i]; e < graph.rowsIndices[i + 1]; e++) {
            out_components.insert(graph.endV[e]);
        }
    }
	
	get_out_component_id(n, out_components, out_component_id, component_id);
	
    for (vertex_id_t i = 0; i < local_n; i++) {
        for (edge_id_t e = graph.rowsIndices[i]; e < graph.rowsIndices[i + 1]; e++) {
            weight_t weight = graph.weights[e];
            local_m += weight;
            vertex_id_t cu = component_id[i], cv = out_component_id[graph.endV[e]];
            if (cu == cv) {
                local_sigma[cu].in += weight;
                local_sigma[cu].tot += weight;
            } else {
                local_sigma[cu].tot += weight;
                local_sigma[cv].tot += weight;
            }
        }
    }
	
    weight_t m;
    MPI_Allreduce(&local_m, &m, 1, MPI_DOUBLE, MPI_SUM, MPI_COMM_WORLD);
	
	int *send_cnt = new int[size];
    int *recv_cnt = new int[size];
    memset(send_cnt, 0, sizeof(int) * size);
    for (sigma_map_t::iterator i = local_sigma.begin(); i != local_sigma.end(); i++) {
        vertex_id_t cu = i->first;
        send_cnt[VERTEX_OWNER(cu, n, size)] += 2;
    }
	
    MPI_Alltoall(send_cnt, 1, MPI_INT, recv_cnt, 1, MPI_INT, MPI_COMM_WORLD);
    
	weight_t **send_buf = new weight_t *[size];
	for (int i = 0; i < size; i++) {
		send_buf[i] = new weight_t[send_cnt[i]];
	}
	
	weight_t **recv_buf = new weight_t *[size];
	for (int i = 0; i < size; i++) {
		recv_buf[i] = new weight_t[recv_cnt[i]];
	}
	
	int **send_buf2 = new int *[size];
	for (int i = 0; i < size; i++) {
		send_buf2[i] = new int[send_cnt[i]];
	}
	
	int **recv_buf2 = new int *[size];
	for (int i = 0; i < size; i++) {
		recv_buf2[i] = new int[recv_cnt[i]];
	}
	
	MPI_Request *requests = new MPI_Request[size];
	for (int i = 0; i < size; i++) {
		MPI_Irecv(recv_buf[i], recv_cnt[i], MPI_DOUBLE, i, 0, MPI_COMM_WORLD, &requests[i]);
	}
	
	MPI_Barrier(MPI_COMM_WORLD);
	
	memset(send_cnt, 0, sizeof(int) * size);
    for (sigma_map_t::iterator i = local_sigma.begin(); i != local_sigma.end(); i++) {
        vertex_id_t cu = i->first;
		int owner = VERTEX_OWNER(cu, n, size);
		send_buf[owner][send_cnt[owner]] = i->second.in;
		send_buf2[owner][send_cnt[owner]++] = 2 * cu;
		send_buf[owner][send_cnt[owner]] = i->second.tot;
		send_buf2[owner][send_cnt[owner]++] = 2 * cu + 1;
    }
	
	for (int i = 0; i < size; i++) {
		MPI_Send(send_buf[i], send_cnt[i], MPI_DOUBLE, i, 0, MPI_COMM_WORLD);
	}
	
	MPI_Waitall(size, requests, MPI_STATUSES_IGNORE);
	
	for (int i = 0; i < size; i++) {
        MPI_Irecv(recv_buf2[i], recv_cnt[i], MPI_INT, i, 0, MPI_COMM_WORLD, &requests[i]);
    }
	
	MPI_Barrier(MPI_COMM_WORLD);
	
	for (int i = 0; i < size; i++) {
		MPI_Send(send_buf2[i], send_cnt[i], MPI_INT, i, 0, MPI_COMM_WORLD);
	}
	
	MPI_Waitall(size, requests, MPI_STATUSES_IGNORE);
	
    for (int i = 0; i < size; i++) {
		for (int j = 0; j < recv_cnt[i]; j++) {
        	weight_t tmp = recv_buf[i][j];
        	int tag = recv_buf2[i][j];
        	vertex_id_t cu = tag / 2;
        	if (tag % 2) {
            	sigma[cu].tot += tmp;
        	} else {
            	sigma[cu].in += tmp;
        	}
		}
    }
	
    weight_t local_q = 0;
    for (sigma_map_t::iterator i = sigma.begin(); i != sigma.end(); i++) {
        local_q += i->second.in / m - pow(i->second.tot / m, 2);
    }
	
    weight_t q;
    MPI_Allreduce(&local_q, &q, 1, MPI_DOUBLE, MPI_SUM, MPI_COMM_WORLD);
	
    delete[] requests;
    delete[] send_cnt;
    delete[] recv_cnt;
	
	free_buf(send_buf, size);
	free_buf(send_buf2, size);
	
	free_buf(recv_buf, size);
	free_buf(recv_buf2, size);
	
    return q;
}

weight_t total_edge_weight(map_graph_t &graph) {
    weight_t m = 0;
    for (map_graph_t::iterator i = graph.begin(); i != graph.end(); i++) {
        for (map_edges_t::iterator j = i->second.begin(); j != i->second.end(); j++) {
            m += j->second;
        }
    }
    
    return m;
}

weight_t modularity(vertex_id_t n, map_graph_t &graph, const vector<vertex_id_t> &component_id)
{
    int size, rank;
    MPI_Comm_size(MPI_COMM_WORLD, &size);
    MPI_Comm_rank(MPI_COMM_WORLD, &rank);
    map<vertex_id_t, vertex_id_t> out_component_id;
    set<vertex_id_t> out_components;
    sigma_map_t local_sigma;
    sigma_map_t sigma;
    weight_t local_m = 0;
	for (map_graph_t::iterator i = graph.begin(); i != graph.end(); i++) {
        for (map_edges_t::iterator j = i->second.begin(); j != i->second.end(); j++) {
            out_components.insert(j->first);
        }
    }
	
    get_out_component_id(n, out_components, out_component_id, component_id);
	
	for (map_graph_t::iterator i = graph.begin(); i != graph.end(); i++) {
        for (map_edges_t::iterator j = i->second.begin(); j != i->second.end(); j++) {
            weight_t weight = j->second;
            local_m += weight;
            vertex_id_t cu = component_id[i->first], cv = out_component_id[j->first];
            if (cu == cv) {
                local_sigma[cu].in += weight;
                local_sigma[cu].tot += weight;
            } else {
                local_sigma[cu].tot += weight;
                local_sigma[cv].tot += weight;
            }
        }
    }
	
    weight_t m;
    MPI_Allreduce(&local_m, &m, 1, MPI_DOUBLE, MPI_SUM, MPI_COMM_WORLD);
	
    int *send_cnt = new int[size];
    int *recv_cnt = new int[size];
    memset(send_cnt, 0, sizeof(int) * size);
    for (sigma_map_t::iterator i = local_sigma.begin(); i != local_sigma.end(); i++) {
        vertex_id_t cu = i->first;
        send_cnt[VERTEX_OWNER(cu, n, size)] += 2;
    }
	
    MPI_Alltoall(send_cnt, 1, MPI_INT, recv_cnt, 1, MPI_INT, MPI_COMM_WORLD);
	
    weight_t **send_buf = new weight_t *[size];
    for (int i = 0; i < size; i++) {
        send_buf[i] = new weight_t[send_cnt[i]];
    }
	
    weight_t **recv_buf = new weight_t *[size];
    for (int i = 0; i < size; i++) {
        recv_buf[i] = new weight_t[recv_cnt[i]];
    }
	
    int **send_buf2 = new int *[size];
    for (int i = 0; i < size; i++) {
        send_buf2[i] = new int[send_cnt[i]];
    }
	
    int **recv_buf2 = new int *[size];
    for (int i = 0; i < size; i++) {
        recv_buf2[i] = new int[recv_cnt[i]];
    }
	
    MPI_Request *requests = new MPI_Request[size];
	for (int i = 0; i < size; i++) {
        MPI_Irecv(recv_buf[i], recv_cnt[i], MPI_DOUBLE, i, 0, MPI_COMM_WORLD, &requests[i]);
    }
	
    MPI_Barrier(MPI_COMM_WORLD);
	
    memset(send_cnt, 0, sizeof(int) * size);
    for (sigma_map_t::iterator i = local_sigma.begin(); i != local_sigma.end(); i++) {
        vertex_id_t cu = i->first;
        int owner = VERTEX_OWNER(cu, n, size);
        send_buf[owner][send_cnt[owner]] = i->second.in;
        send_buf2[owner][send_cnt[owner]++] = 2 * cu;
        send_buf[owner][send_cnt[owner]] = i->second.tot;
        send_buf2[owner][send_cnt[owner]++] = 2 * cu + 1;
    }
	
    for (int i = 0; i < size; i++) {
        MPI_Send(send_buf[i], send_cnt[i], MPI_DOUBLE, i, 0, MPI_COMM_WORLD);
    }
	
    MPI_Waitall(size, requests, MPI_STATUSES_IGNORE);
	
    for (int i = 0; i < size; i++) {
        MPI_Irecv(recv_buf2[i], recv_cnt[i], MPI_INT, i, 0, MPI_COMM_WORLD, &requests[i]);
    }
	
    MPI_Barrier(MPI_COMM_WORLD);
	
    for (int i = 0; i < size; i++) {
        MPI_Send(send_buf2[i], send_cnt[i], MPI_INT, i, 0, MPI_COMM_WORLD);
    }
	
    MPI_Waitall(size, requests, MPI_STATUSES_IGNORE);
	
    for (int i = 0; i < size; i++) {
        for (int j = 0; j < recv_cnt[i]; j++) {
            weight_t tmp = recv_buf[i][j];
            int tag = recv_buf2[i][j];
            vertex_id_t cu = tag / 2;
            if (tag % 2) {
                sigma[cu].tot += tmp;
            } else {
                sigma[cu].in += tmp;
            }
        }
    }
	
    weight_t local_q = 0;
    for (sigma_map_t::iterator i = sigma.begin(); i != sigma.end(); i++) {
        local_q += i->second.in / m - pow(i->second.tot / m, 2);
    }
	
    weight_t q;
    MPI_Allreduce(&local_q, &q, 1, MPI_DOUBLE, MPI_SUM, MPI_COMM_WORLD);
	
    delete[] requests;
    delete[] send_cnt;
    delete[] recv_cnt;
	
    free_buf(send_buf, size);
    free_buf(send_buf2, size);
	
    free_buf(recv_buf, size);
    free_buf(recv_buf2, size);
	
    return q;
}

/* FIXME */
void build_map_graph_structure(graph_t &graph, map_graph_t &map_graph) {
    for (vertex_id_t u = 0; u < graph.local_n; u++) {
        for (edge_id_t e = graph.rowsIndices[u]; e < graph.rowsIndices[u + 1]; e++) {
            vertex_id_t v = graph.endV[e];
            map_graph[u][v] = 0;
        }
    }
}

/* FIXME */
void set_map_graph_weights(graph_t &graph, map_graph_t &map_graph) {
    for (vertex_id_t u = 0; u < graph.local_n; u++) {
        for (edge_id_t e = graph.rowsIndices[u]; e < graph.rowsIndices[u + 1]; e++) {
            vertex_id_t v = graph.endV[e];
            weight_t weight = graph.weights[e];
            map_graph[u][v] += weight;
        }
    }
}

void init_component_id(vertex_id_t n, int size, int rank, map_graph_t &graph, vector<vertex_id_t> &component_id) {
    for (map_graph_t::iterator i = graph.begin(); i != graph.end(); i++) {
        component_id[i->first] = VERTEX_TO_GLOBAL(i->first, n, size, rank);
    }
}

void calculate_incident_edge_weights(vertex_id_t n, int size, int rank, map_graph_t &graph, vector<weight_t> &weights) {
    for (map_graph_t::iterator i = graph.begin(); i != graph.end(); i++) {
        vertex_id_t u = i->first;
        weights[u] = 0;
        for (map_edges_t::iterator j = i->second.begin(); j != i->second.end(); j++) {
            vertex_id_t v = j->first;
            weight_t weight = j->second;
            weights[u] += (VERTEX_TO_GLOBAL(u, n, size, rank) == v ? 1 : 2) * weight;
        }
    }
}

void compress(vertex_id_t n, map_graph_t &graph, vector<vertex_id_t> &component_id)
{
    int size, rank;
    MPI_Comm_size(MPI_COMM_WORLD, &size);
    MPI_Comm_rank(MPI_COMM_WORLD, &rank);
    map_graph_t local_new_graph, new_graph;
	
	map<vertex_id_t, vertex_id_t> out_component_id;
    set<vertex_id_t> out_components;
    for (map_graph_t::iterator i = graph.begin(); i != graph.end(); i++) {
        for (map_edges_t::iterator j = i->second.begin(); j != i->second.end(); j++) {
            out_components.insert(j->first);
        }
    }
	
	get_out_component_id(n, out_components, out_component_id, component_id);
	
    for (map_graph_t::iterator i = graph.begin(); i != graph.end(); i++) {
        vertex_id_t cu = component_id[i->first];
        for (map_edges_t::iterator j = i->second.begin(); j != i->second.end(); j++) {
            vertex_id_t cv = out_component_id[j->first];
            weight_t weight = j->second;
            local_new_graph[cu][cv] += weight;
        }
    }
    
    int *send_cnt = new int[size];
    int *recv_cnt = new int[size];
    memset(send_cnt, 0, sizeof(int) * size);
    for (map_graph_t::iterator i = local_new_graph.begin(); i != local_new_graph.end(); i++) {
        vertex_id_t cu = i->first;
        send_cnt[VERTEX_OWNER(cu, n, size)] += i->second.size();
    }
    
    MPI_Alltoall(send_cnt, 1, MPI_INT, recv_cnt, 1, MPI_INT, MPI_COMM_WORLD);
    tmp_struct tmp;
    int blocklengths[3] = {1, 1, 1};
    MPI_Aint addresses[4];
    MPI_Address(&tmp, &addresses[0]);
    MPI_Address(&tmp.v, &addresses[1]);
	MPI_Address(&tmp.c, &addresses[2]);
    MPI_Address(&tmp.w, &addresses[3]);
    MPI_Aint displacements[3];
    for (int i = 0; i < 3; i++) {
        displacements[i] = addresses[i + 1] - addresses[0];
    }
    
    MPI_Datatype MY_T;
    MPI_Datatype types[3] = {MPI_INT, MPI_INT, MPI_DOUBLE};
    MPI_Type_struct(3, blocklengths, displacements, types, &MY_T);
    MPI_Type_commit(&MY_T);
    
	tmp_struct **recv_buf = new tmp_struct *[size];
	for (int i = 0; i < size; i++) {
		recv_buf[i] = new tmp_struct[recv_cnt[i]];
	}
	
	MPI_Request *requests = new MPI_Request[size];
	for (int i = 0; i < size; i++) {
		MPI_Irecv(recv_buf[i], recv_cnt[i], MY_T, i, 0, MPI_COMM_WORLD, &requests[i]);
	}
	
	MPI_Barrier(MPI_COMM_WORLD);
	
    tmp_struct **send_buf = new tmp_struct *[size];
	for (int i = 0; i < size; i++) {
		send_buf[i] = new tmp_struct[send_cnt[i]];
	}
	
	memset(send_cnt, 0, sizeof(int) * size);
    for (map_graph_t::iterator i = local_new_graph.begin(); i != local_new_graph.end(); i++) {
        vertex_id_t cu = i->first;
    	for (map_edges_t::iterator j = i->second.begin(); j != i->second.end(); j++) {
            int owner = VERTEX_OWNER(cu, n, size);
			tmp_struct local_buf;
			local_buf.v = j->first;
			local_buf.c = cu;
            local_buf.w = j->second;
			send_buf[owner][send_cnt[owner]++] = local_buf;
        }
	}
	
	for (int i = 0; i < size; i++) {
		MPI_Send(send_buf[i], send_cnt[i], MY_T, i, 0, MPI_COMM_WORLD);
	}
	
	MPI_Waitall(size, requests, MPI_STATUSES_IGNORE);
	for (int i = 0; i < size; i++) {
		for (int j = 0; j < recv_cnt[i]; j++) {
			vertex_id_t cv = recv_buf[i][j].v;
        	weight_t w = recv_buf[i][j].w;
        	vertex_id_t cu = recv_buf[i][j].c;
        	new_graph[VERTEX_LOCAL(cu, n, size, rank)][cv] += w;
		}
	}
	
    MPI_Barrier(MPI_COMM_WORLD);
    MPI_Type_free(&MY_T);
    
    graph = new_graph;
    
    delete[] send_cnt;
    delete[] recv_cnt;
	delete[] requests;
	
	free_buf(send_buf, size);
	free_buf(recv_buf, size);
}

void translate(vertex_id_t n, vector<vertex_id_t> &component_id, vector<vertex_id_t> &new_component_id)
{
	int size, rank;
    MPI_Comm_size(MPI_COMM_WORLD, &size);
    MPI_Comm_rank(MPI_COMM_WORLD, &rank);
    map<vertex_id_t, vertex_id_t> out_component_id;
    set<vertex_id_t> out_components;
    for (vector<vertex_id_t>::iterator i = component_id.begin(); i != component_id.end(); i++) {
        out_components.insert(*i);
    }
    
	get_out_component_id(n, out_components, out_component_id, new_component_id);
	
    for (vector<vertex_id_t>::iterator i = component_id.begin(); i != component_id.end(); i++) {
        *i = out_component_id[*i];
    }
}

void propagation(vertex_id_t n, map_graph_t &in_table, map_graph_t &out_table, vector<vertex_id_t> &component_id)
{
	//cout << "step1" << endl;
    int size, rank;
    MPI_Comm_size(MPI_COMM_WORLD, &size);
	MPI_Comm_rank(MPI_COMM_WORLD, &rank);
    
	tmp_struct tmp;
	
    int blocklengths[3] = {1, 1, 1};
    MPI_Aint addresses[4];
    MPI_Address(&tmp, &addresses[0]);
    MPI_Address(&tmp.v, &addresses[1]);
    MPI_Address(&tmp.c, &addresses[2]);
    MPI_Address(&tmp.w, &addresses[3]);
    MPI_Aint displacements[3];
    for (int i = 0; i < 3; i++) {
        displacements[i] = addresses[i + 1] - addresses[0];
    }
    
    MPI_Datatype MY_T;
    MPI_Datatype types[3] = {MPI_INT, MPI_INT, MPI_DOUBLE};
    MPI_Type_struct(3, blocklengths, displacements, types, &MY_T);
    MPI_Type_commit(&MY_T);
    
    int *send_cnt = new int[size];
    int *recv_cnt = new int[size];
    memset(send_cnt, 0, sizeof(int) * size);
	//cout << "step2" << endl;
    for (map_graph_t::iterator i = in_table.begin(); i != in_table.end(); i++) {
        for (map_edges_t::iterator j = i->second.begin(); j != i->second.end(); j++) {
            send_cnt[VERTEX_OWNER(i->first, n, size)]++;
        }
    }
    
	//cout << "step3" << endl;
    MPI_Alltoall(send_cnt, 1, MPI_INT, recv_cnt, 1, MPI_INT, MPI_COMM_WORLD);
    
	//cout << "step4" << endl;
	tmp_struct **recv_buf = new tmp_struct *[size];
	for (int i = 0; i < size; i++) {
		recv_buf[i] = new tmp_struct[recv_cnt[i]];
	}
	
	MPI_Request *requests = new MPI_Request[size];
	
	//cout << "step5" << endl;
	for (int i = 0; i < size; i++) {
		MPI_Irecv(recv_buf[i], recv_cnt[i], MY_T, i, 0, MPI_COMM_WORLD, &requests[i]);
	}
	
	//cout << "step6" << endl;
	MPI_Barrier(MPI_COMM_WORLD);
	
	//cout << "step7" << endl;
    tmp_struct **send_buf = new tmp_struct *[size];
	for (int i = 0; i < size; i++) {
		send_buf[i] = new tmp_struct[send_cnt[i]];
	}
	
	memset(send_cnt, 0, sizeof(int) * size);
	for (map_graph_t::iterator i = in_table.begin(); i != in_table.end(); i++) {
        for (map_edges_t::iterator j = i->second.begin(); j != i->second.end(); j++) {
			tmp_struct local_buf;
			local_buf.v = i->first;
            local_buf.c = component_id[j->first];
            local_buf.w = j->second;
			
			int owner = VERTEX_OWNER(local_buf.v, n, size);
			send_buf[owner][send_cnt[owner]++] = local_buf;
        }
    }
    
	//cout << "step8" << endl;
	for (int i = 0; i < size; i++) {
		MPI_Send(send_buf[i], send_cnt[i], MY_T, i, 0, MPI_COMM_WORLD);
	}
	
	//cout << "step9" << endl;
	MPI_Waitall(size, requests, MPI_STATUSES_IGNORE);
	
	//cout << "step10" << endl;
	for (int i = 0; i < size; i++) {
		for (int j = 0; j < recv_cnt[i]; j++) {
			vertex_id_t u = recv_buf[i][j].v;
        	vertex_id_t c = recv_buf[i][j].c;
        	weight_t w = recv_buf[i][j].w;
        	out_table[VERTEX_LOCAL(u, n, size, rank)][c] += w;
		}
	}
	
	//cout << "step11" << endl;
    MPI_Barrier(MPI_COMM_WORLD);
    MPI_Type_free(&MY_T);
    
	//cout << "step12" << endl;
    delete[] send_cnt;
    delete[] recv_cnt;
	delete[] requests;
	free_buf(send_buf, size);
	free_buf(recv_buf, size);
}

void get_out_sigma_tot(vertex_id_t n, map_edges_t &out_sigma_tot, map_graph_t &out_table, vector<weight_t> &sigma_tot)
{
	//cout << "Q" << endl;
    int size, rank;
    MPI_Comm_size(MPI_COMM_WORLD, &size);
    MPI_Comm_rank(MPI_COMM_WORLD, &rank);
    set<vertex_id_t> out_components;
    for (map_graph_t::iterator i = out_table.begin(); i != out_table.end(); i++) {
        vertex_id_t u = i->first;
        for (map_edges_t::iterator j = i->second.begin(); j != i->second.end(); j++) {
            vertex_id_t c = j->first;
            out_components.insert(c);
        }
    }
    
	//cout << "W" << endl;
    int *send_cnt = new int[size];
    int *recv_cnt = new int[size];
    memset(send_cnt, 0, sizeof(int) * size);
	for (set<vertex_id_t>::iterator i = out_components.begin(); i != out_components.end(); i++) {
        send_cnt[VERTEX_OWNER(*i, n, size)]++;
    }
	
	vertex_id_t **send_buf = new vertex_id_t *[size];
	for (int i = 0; i < size; i++) {
		send_buf[i] = new vertex_id_t[send_cnt[i]];
	}
	
	MPI_Alltoall(send_cnt, 1, MPI_INT, recv_cnt, 1, MPI_INT, MPI_COMM_WORLD);
	vertex_id_t **recv_buf = new vertex_id_t *[size];
	for (int i = 0; i < size; i++) {
		recv_buf[i] = new vertex_id_t[recv_cnt[i]];
	}
	
    MPI_Request *requests = new MPI_Request[size];
	for (int i = 0; i < size; i++) {
		MPI_Irecv(recv_buf[i], recv_cnt[i], MPI_INT, i, 0, MPI_COMM_WORLD, &requests[i]);
	}
	
	MPI_Barrier(MPI_COMM_WORLD);
	
    //cout << "E" << endl;
	memset(send_cnt, 0, sizeof(int) * size);
	for (set<vertex_id_t>::iterator i = out_components.begin(); i != out_components.end(); i++) {
        int owner = VERTEX_OWNER(*i, n, size);
		send_buf[owner][send_cnt[owner]++] = *i;
    }
    
	for (int i = 0; i < size; i++) {
		MPI_Send(send_buf[i], send_cnt[i], MPI_INT, i, 0, MPI_COMM_WORLD);
	}
	
	//cout << "R" << endl;
	MPI_Waitall(size, requests, MPI_STATUSES_IGNORE);
    //cout << "T" << endl;
	
    weight_t **send_buf2 = new weight_t *[size];
	for (int i = 0; i < size; i++) {
		send_buf2[i] = new weight_t[recv_cnt[i]];
	}
	
	weight_t **recv_buf2 = new weight_t *[size];
	for (int i = 0; i < size; i++) {
		recv_buf2[i] = new weight_t[send_cnt[i]];
	}
	
	for (int i = 0; i < size; i++) {
		MPI_Irecv(recv_buf2[i], send_cnt[i], MPI_DOUBLE, i, 0, MPI_COMM_WORLD, &requests[i]);
	}
	
	MPI_Barrier(MPI_COMM_WORLD);
	
	for (int i = 0; i < size; i++) {
		for (int j = 0; j < recv_cnt[i]; j++) {
			send_buf2[i][j] = sigma_tot[VERTEX_LOCAL(recv_buf[i][j], n, size, rank)];
		}
    }
    
	for (int i = 0; i < size; i++) {
		MPI_Send(send_buf2[i], recv_cnt[i], MPI_DOUBLE, i, 0, MPI_COMM_WORLD);
	}
	
	MPI_Waitall(size, requests, MPI_STATUSES_IGNORE);
	//cout << "U" << endl;
	for (int i = 0; i < size; i++) {
		for (int j = 0; j < send_cnt[i]; j++) {
			out_sigma_tot[send_buf[i][j]] = recv_buf2[i][j];
		}
	}
	
	//cout << "I" << endl;
    MPI_Barrier(MPI_COMM_WORLD);
    
    delete[] send_cnt;
    delete[] recv_cnt;
    delete[] requests;
    
	free_buf(send_buf, size);
	free_buf(send_buf2, size);
	
	free_buf(recv_buf, size);
	free_buf(recv_buf2, size);
}

void sigma_update(vertex_id_t n, map_edges_t &sigma_chg, vector<weight_t> &sigma)
{
    int rank, size;
    MPI_Comm_size(MPI_COMM_WORLD, &size);
    MPI_Comm_rank(MPI_COMM_WORLD, &rank);
    
    int *send_cnt = new int[size];
    int *recv_cnt = new int[size];
    memset(send_cnt, 0, sizeof(int) * size);
	for (map_edges_t::iterator i = sigma_chg.begin(); i != sigma_chg.end(); i++) {
        vertex_id_t c = i->first;
        send_cnt[VERTEX_OWNER(c, n, size)]++;
    }
	
	MPI_Alltoall(send_cnt, 1, MPI_INT, recv_cnt, 1, MPI_INT, MPI_COMM_WORLD);
	
	weight_t **send_buf = new weight_t *[size];
	for (int i = 0; i < size; i++) {
		send_buf[i] = new weight_t[send_cnt[i]];
	}
	
	vertex_id_t **send_buf2 = new vertex_id_t *[size];
	for (int i = 0; i < size; i++) {
		send_buf2[i] = new vertex_id_t[send_cnt[i]];
	}
	
	weight_t **recv_buf = new weight_t *[size];
    for (int i = 0; i < size; i++) {
        recv_buf[i] = new weight_t[recv_cnt[i]];
    }                             
                                  
    vertex_id_t **recv_buf2 = new vertex_id_t *[size];
    for (int i = 0; i < size; i++) {
        recv_buf2[i] = new vertex_id_t[recv_cnt[i]];
    }
	
	memset(send_cnt, 0, sizeof(int) * size);
    for (map_edges_t::iterator i = sigma_chg.begin(); i != sigma_chg.end(); i++) {
        vertex_id_t c = i->first;
        int owner = VERTEX_OWNER(c, n, size);
		send_buf[owner][send_cnt[owner]] = i->second;
		send_buf2[owner][send_cnt[owner]++] = c;
    }
    
	MPI_Request *requests = new MPI_Request[size];
	for (int i = 0; i < size; i++) {
		MPI_Irecv(recv_buf[i], recv_cnt[i], MPI_DOUBLE, i, 0, MPI_COMM_WORLD, &requests[i]);
	}
	
	MPI_Barrier(MPI_COMM_WORLD);
	
	for (int i = 0; i < size; i++) {
		MPI_Send(send_buf[i], send_cnt[i], MPI_DOUBLE, i, 0, MPI_COMM_WORLD);
	}
	
	MPI_Waitall(size, requests, MPI_STATUSES_IGNORE);
	
	for (int i = 0; i < size; i++) {
        MPI_Irecv(recv_buf2[i], recv_cnt[i], MPI_INT, i, 0, MPI_COMM_WORLD, &requests[i]);
    }           
                
    MPI_Barrier(MPI_COMM_WORLD);
	
    for (int i = 0; i < size; i++) {
        MPI_Send(send_buf2[i], send_cnt[i], MPI_INT, i, 0, MPI_COMM_WORLD);
    }                                                       
                                                            
    MPI_Waitall(size, requests, MPI_STATUSES_IGNORE);
	
	for (int i = 0; i < size; i++) {
		for (int j = 0; j < recv_cnt[i]; j++) {
        	sigma[VERTEX_LOCAL(recv_buf2[i][j], n, size, rank)] += recv_buf[i][j];
		}
	}
	
    MPI_Barrier(MPI_COMM_WORLD);
    
    delete[] send_cnt;
    delete[] recv_cnt;
	delete[] requests;
	
	free_buf(send_buf, size);
	free_buf(send_buf2, size);
	free_buf(recv_buf, size);
	free_buf(recv_buf2, size);
}

/* FIXME */
void louvain(vertex_id_t n, vertex_id_t local_n, map_graph_t graph, vector<vertex_id_t> &component_id)
{
    int rank, size;
    MPI_Comm_size(MPI_COMM_WORLD, &size);
    MPI_Comm_rank(MPI_COMM_WORLD, &rank);
    
	for (map_graph_t::iterator i = graph.begin(); i != graph.end(); i++) {
        for (map_edges_t::iterator j = i->second.begin(); j != i->second.end(); j++) {
            vertex_id_t u = i->first;
            vertex_id_t v = j->first;
            weight_t weight = j->second;
            //cout << "rank = " << rank << " u = " << u << " v = " << v << " w = " << weight << endl;
        }
    }
	
	
    // initially each vertex is in its own component
    init_component_id(n, size, rank, graph, component_id);
    
    // get the sum of all edge weights (NB: not divided by 2!)
    weight_t local_sum_w = total_edge_weight(graph), m;
    MPI_Allreduce(&local_sum_w, &m, 1, MPI_DOUBLE, MPI_SUM, MPI_COMM_WORLD);
    
	//cout << "sum weight = m = " << m << endl;
	
    // calculate initial sigma_total
    vector<weight_t> sigma_in(local_n, 0), sigma_tot(local_n, 0), w(local_n, 0);
    calculate_incident_edge_weights(n, size, rank, graph, sigma_tot);
    
    map_graph_t in_table;
    map_graph_t out_table;
    
    for (map_graph_t::iterator i = graph.begin(); i != graph.end(); i++) {
        for (map_edges_t::iterator j = i->second.begin(); j != i->second.end(); j++) {
            vertex_id_t u = i->first;
            vertex_id_t v = j->first;
            weight_t weight = j->second;
            in_table[v][u] = weight;
        }
    }
    
    while (true) {
		//cout << "outer iter..." << endl;
        // initialize component mapping for current graph
        vector<vertex_id_t> new_component_id(component_id);
        init_component_id(n, size, rank, graph, new_component_id);
        
        // calculate weights of vertices in current graph
        calculate_incident_edge_weights(n, size, rank, graph, w);
        
        // stage: try to move each vertex into neighbour's community
        
        bool more, changed = false, finish = false;
        
		//cout << "before propagation" << endl;
        propagation(n, in_table, out_table, new_component_id);
        //cout << "after propagation" << endl;
		
        double cur_q = modularity(n, graph, new_component_id);
		//cout << "out iter, modularity = " << cur_q << endl;
        int iter = 0;
        do {
			//cout << "after do" << endl;
            iter++;
            more = false;
            
            vector<weight_t> mu(local_n, 0);
            vector<vertex_id_t> cu(new_component_id);
            
            map_edges_t out_sigma_tot;
            //cout << "before get_out_sigma_tot" << endl;
			get_out_sigma_tot(n, out_sigma_tot, out_table, sigma_tot);
            //cout << "step1" << endl;
            for (map_graph_t::iterator i = out_table.begin(); i != out_table.end(); i++) {
                vertex_id_t u = i->first;
                for (map_edges_t::iterator j = i->second.begin(); j != i->second.end(); j++) {
                    vertex_id_t c = j->first;
                    weight_t wc = j->second;
                    if ((wc / m - (out_sigma_tot[c] * w[u]) / (pow(m, 2))) > mu[u]) {
                        cu[u] = c;
                        mu[u] = (wc / m - (out_sigma_tot[c] * w[u]) / (pow(m, 2)));
                    }
                }
            }
            
            map_edges_t sigma_tot_chg;
            for (map_graph_t::iterator i = out_table.begin(); i != out_table.end(); i++) {
                vertex_id_t u = i->first;
                if (mu[u] > 0) {
                    sigma_tot_chg[new_component_id[u]] -= w[u];
                    new_component_id[u] = cu[u];
                    sigma_tot_chg[cu[u]] += w[u];
                }
            }
            
			//cout << "step2" << endl;
			//cout << "STEP1" << endl;
            sigma_update(n, sigma_tot_chg, sigma_tot);
			//cout << "STEP.1.5" << endl;
			//cout << "step3" << endl;
            propagation(n, in_table, out_table, new_component_id);
            
			//cout << "STEP2" << endl;
			//cout << "step4" << endl;
            map_edges_t sigma_in_chg;
            for (map_graph_t::iterator i = out_table.begin(); i != out_table.end(); i++) {
                vertex_id_t u = i->first;
                for (map_edges_t::iterator j = i->second.begin(); j != i->second.end(); j++) {
                    vertex_id_t c = j->first;
                    weight_t wc = j->second;
                    if (new_component_id[u] == c) {
                        sigma_in_chg[c] += wc;
                    }
                }
            }
            
			//cout << "step5" << endl;
            sigma_update(n, sigma_in_chg, sigma_in);
            double q = modularity(n, graph, new_component_id);
			if (!rank) {
				cout << "modularity = " << q << endl;
			}
			
            if (q > cur_q) {
				if (q - cur_q > eps) {
                	cur_q = q;
                	more = true;
				} else {
					more = false;
					changed = true;
					finish = true;
				}
            }
            
            changed |= more;
        } while (more);
        
        if (!changed) {
            // no vertices have been moved, the algorithm has converged - exit
            return;
        }
        
		//cout << "WERWERDFSD" << endl;
		
        // build compressed graph
        compress(n, graph, new_component_id);
		
		//cout << "AFTER compress" << endl;
		
        // update component ids of vertices not in current graph
        translate(n, component_id, new_component_id);
		
		//cout << "BBBBBBBBBNNNNNNNNNN" << endl;
		if (finish) {
			break;
		}
    }
}

void init(int argc, char **argv, graph_t *G)
{
    if (argc == 1) {
        cout << "Error" << endl;
        exit(1);
    }
    
    MPI_Init(&argc, &argv);
    MPI_Comm_size(MPI_COMM_WORLD, &G->nproc);
    MPI_Comm_rank(MPI_COMM_WORLD, &G->rank);
    
    for (int i = 1; i < argc; ++i) {
        if (!strcmp(argv[i], "-in")) {
            int l = strlen(argv[++i]);
            strncpy(filename, argv[i], (l > FNAME_LEN - 1 ? FNAME_LEN - 1 : l) );
        }
    }
}

int main(int argc, char **argv)
{
    graph_t graph;
    init(argc, argv, &graph);
    //cout << "before read graph" << endl;
	readGraph_singleFile_MPI(&graph, filename);
    //cout << "after read graph" << endl;
	
    // preprocess 1 (without using weights)
    // you can make a convenient form of a graph
    map_graph_t map_graph;
    //cout << "before build" << endl;
	build_map_graph_structure(graph, map_graph);
    //cout << "after build" << endl;
	
    // preprocess 2 (using weights, no structure change)
    // placement of weights
    //cout << "before set" << endl;
	set_map_graph_weights(graph, map_graph);
    //cout << "after set" << endl;
	
    // find communities
    vector<vertex_id_t> component_id(graph.local_n);
    MPI_Barrier(MPI_COMM_WORLD);
    double start_time = MPI_Wtime();
    //cout << "before louvain" << endl;
	louvain(graph.n, graph.local_n, map_graph, component_id);
    MPI_Barrier(MPI_COMM_WORLD);
    //cout << "after louvain" << endl;
	double time = MPI_Wtime() - start_time;
    if (!graph.rank) {
		cout.precision(6);
    	cout << "time: " << fixed << time << " seconds" << endl;
	}
	
    // verify solution (compute modularity)
    double final_modularity = modularity(graph, component_id);
    if (!graph.rank) {
		cout << "modularity: " << final_modularity << endl;
	}
	
    double ratio = final_modularity / time;
    if (!graph.rank) {
		cout << "rating: " << fixed << ratio << endl;
	}
    
    MPI_Finalize();
    return 0;
}

