/* ============================================================================================== */
/* GraphHPC-2016 LFR* : Lancichinetti-Fortunato-Radicchi (LFR) benchmark with slight modification */
/* ============================================================================================== */

#include "defs.h"

/* !!!FIXME: it is strongly recommended to adjust parameters when scale <= 15 !!! */
/* portion of outside vertex edges */
#define MU_DEFAULT 0.5


#define MIN_COMMUNITY_SIZE 10
/* one community size may be more than MAX_COMMUNITY_SIZE, if at the last step
   of generation, count of remaining vertices < MIN_COMMUNITY_SIZE */
#define MAX_COMMUNITY_SIZE 100000

/* must be < MIN_COMMUNITY_SIZE */
#define MIN_VERTEX_DEGREE 2

/* must be < MAX_COMMUNITY_SIZE */
#define MAX_VERTEX_DEGREE 25000

/* community size is in [MIN_COMMUNITY_SIZE; MAX_COMMUNITY_SIZE], fraction P(s) of communities having size s is P(s) ~ s^(-COMMUNITY_TAU) */
#define COMMUNITY_TAU 2.6

/* vertex degree is in [MIN_VERTEX_DEGREE; MAX_VERTEX_DEGREE], fraction P(k) of vertex having k connections to other vertices is P(k) ~ k^(-DEGREE_TAU) */
#define DEGREE_TAU 1.99

char outFilename[FNAME_LEN];
double mu = MU_DEFAULT;

/* helper */
void usage(int argc, char **argv)
{
    printf("LFR graph generator\n");
    printf("Options:\n");
    printf("   -s <scale>, number of vertices is 2^<scale>\n");
    printf("   -out <filename>, lfr-<s> is default value\n");
    printf("   -mu <value>, 0 <= mu <= 1, mu default value = 0.5\n");
    exit(1);
}

/* init */
void init(int argc, char **argv, graph_t *G)
{
    G->scale = -1;
    G->directed = false;
    G->min_weight = 0.0;
    G->max_weight = 1.0;
    int l;
    int no_file_name = 1;
    if (argc == 1) {
        usage(argc, argv);
    }
    
    for (int i = 1; i < argc; ++i) {
        if (!strcmp(argv[i], "-s")) {
            G->scale = (int)atoi(argv[++i]);
        }
        
        if (!strcmp(argv[i], "-out")) {
            no_file_name = 0;
            l = strlen(argv[++i]);
            strncpy(outFilename, argv[i], (l > FNAME_LEN-1 ? FNAME_LEN-1 : l) );
        }
        
        if (!strcmp(argv[i], "-h")) {
              usage(argc, argv);
        }
        
        if (!strcmp(argv[i], "-mu")) {
            sscanf(argv[++i], "%lf", &mu);
        }
    }
    
    if (no_file_name) {
        sprintf(outFilename, "lfr-%d", G->scale);
    }
    
    G->n = 1 << G->scale;
}

/* calc values for power law */
void power_law(vertex_id_t smin, vertex_id_t smax, double tau, vector<double> &vals)
{
    vals.clear();
    double sum = 0;
    for (vertex_id_t i = smin; i <= smax; i++) {
        sum += pow((1.0 / i), tau);
    }
    
    double p = 0;
    for (vertex_id_t i = smin; i <= smax; i++) {
        p += pow((1.0 / i), tau);
        vals.push_back(p / sum);
    }
}

bool cmp(const vertex_id_t &x, const vertex_id_t &y)
{
    return x > y;
}

void gen_LFR_graph(graph_t *g)
{
    vertex_id_t TotVertices;
    vector<vertex_id_t> clusterSizes;
    vertex_id_t totClusters = 0;
    vertex_id_t *startVertex, *endVertex;
    edge_id_t numEdges;
    weight_t *weights;
    weight_t MinWeight, MaxWeight;
    vertex_id_t smin = MIN_COMMUNITY_SIZE, smax = MAX_COMMUNITY_SIZE;
    vertex_id_t dmin = MIN_VERTEX_DEGREE, dmax = MAX_VERTEX_DEGREE;
    cout << "community size: smin = " << smin << " smax = " << smax << endl;
    cout << "vertex degree:  dmin = " << dmin << " dmax = " << dmax << endl;
    double community_tau = COMMUNITY_TAU, degree_tau = DEGREE_TAU;
    edge_id_t edgeNum;
    vertex_id_t t1, t2;
    vertex_id_t *permV;
    
    /*----------------------------------------------*/
    /*                 initialize                   */
    /*----------------------------------------------*/
    
    cout << "initialize..." << endl;
    
    srand48(time(NULL));
    
    MinWeight = g->min_weight;
    MaxWeight = g->max_weight;
    TotVertices = g->n; 
    
    /*----------------------------------------------*/
    /*      generate degrees of all vertex          */
    /*----------------------------------------------*/
    
    cout << "generate degrees of all vertex..." << endl;
    
    vector<double> vals;
    vector<vertex_id_t> degree;
    power_law(dmin, dmax, degree_tau, vals);
    for (vertex_id_t i = 0; i < TotVertices; i++) {
        vertex_id_t cur_degree = lower_bound( vals.begin(), vals.end(), drand48() ) - vals.begin() + dmin;
        degree.push_back(cur_degree);
    }
    
    sort(degree.begin(), degree.end(), cmp);
    
    //for (int i = 0; i < degree.size(); i++) {
    //    cout << i << " " << degree[i] << endl;
    //}
    
    /*----------------------------------------------*/
    /*             generate clusters                */
    /*----------------------------------------------*/
    
    cout << "generate cluster sizes..." << endl;
    
    power_law(smin, smax, community_tau, vals);
    vertex_id_t cur_n = 0;
    while (cur_n < TotVertices) {
        vertex_id_t cur_size = lower_bound( vals.begin(), vals.end(), drand48() ) - vals.begin() + smin;
        if (TotVertices - (cur_n + cur_size) < smin) {
            cur_size = TotVertices - cur_n;
        }
        
        clusterSizes.push_back(cur_size);
        cur_n += cur_size;
    }
    
    totClusters = clusterSizes.size();
    
    /*--------------------------------------------------*/
    /*  calc corresponds between vertices and clusters  */
    /*--------------------------------------------------*/
    
    cout << "calc corresponds between vertices and clusters..." << endl;
    
    vector< vector<vertex_id_t> > vertices_in_cluster(totClusters);
    vector<vertex_id_t> cluster_num(TotVertices);
    vector<vertex_id_t> cur_cluster_size(totClusters, 0);
    vector<vertex_id_t> norm_clusters;
    for (vertex_id_t i = 0; i < totClusters; i++) {
        norm_clusters.push_back(i);
    }
    
    vertex_id_t norm_cluster_cnt = totClusters;
    for (vertex_id_t i = 0; i < TotVertices; i++) {
        vertex_id_t deg = degree[i];
        while (true) {
            vertex_id_t id = drand48() * norm_cluster_cnt;
            vertex_id_t num = norm_clusters[id];
            if (clusterSizes[num] > deg) {
                cluster_num[i] = num;
                vertices_in_cluster[num].push_back(i);
                cur_cluster_size[num]++;
                if (cur_cluster_size[num] == clusterSizes[num]) {
                    norm_clusters.erase(norm_clusters.begin() + id);
                    norm_cluster_cnt--;
                }
                
                break;
            }
        }
    }
    
    /*------------------------------------------------------*/
    /*                   generate edges                     */
    /*------------------------------------------------------*/
    
    cout << "generate edges..." << endl;
    
    edgeNum = 0;
    vector<vertex_id_t> startV;
    vector<vertex_id_t> endV;
    
    for (vertex_id_t i = 0; i < TotVertices; i++) {
        vertex_id_t deg = degree[i];
        vertex_id_t outside_cnt = deg * mu;
        vertex_id_t inside_cnt = deg - outside_cnt;
        vertex_id_t num = cluster_num[i];
        vertex_id_t size = clusterSizes[num];
        for (vertex_id_t j = 0; j < inside_cnt; j++) {
            vertex_id_t k = size * drand48();
            vertex_id_t v = vertices_in_cluster[num][k];
            startV.push_back(i);
            endV.push_back(v);
            edgeNum++;
        }
        
        for (vertex_id_t j = 0; j < outside_cnt; j++) {
            vertex_id_t v;
            while (true) {
                v = TotVertices * drand48();
                if (cluster_num[v] != num) {
                    break;
                }
            }
            
            startV.push_back(i);
            endV.push_back(v);
            edgeNum++;
        }
    }
    
    numEdges = edgeNum;
    
    
    /*--------------------------------------------------------------*/
    /*                      shuffle vertices                        */
    /*--------------------------------------------------------------*/
    
    cout << "shuffle vertices..." << endl;
    
    permV = (vertex_id_t *)malloc(TotVertices * sizeof(vertex_id_t));
    startVertex = (vertex_id_t *)malloc(numEdges * sizeof(vertex_id_t));
    endVertex = (vertex_id_t *)malloc(numEdges * sizeof(vertex_id_t));
    for(vertex_id_t i = 0; i < TotVertices; i++) {
        permV[i] = i;
    }
    
    for (vertex_id_t i = 0; i < TotVertices; i++) {
        t1 = i + drand48() * (TotVertices - i);
        if (t1 != i) {
            t2 = permV[t1];
            permV[t1] = permV[i];
            permV[i] = t2;
        }
    }
    
    for (edge_id_t i = 0; i < numEdges; i++) {
        startVertex[i] = permV[startV[i]];
        endVertex[i] = permV[endV[i]];
    }
    
    free(permV);
    
    /*--------------------------------------------------------------*/
    /*                       shuffle edges                          */
    /*--------------------------------------------------------------*/
    
    cout << "shuffle edges..." << endl;
    cout << "number of edges in the graph: " << numEdges << endl;
    
    for (edge_id_t i = 0; i < numEdges; i++) {
        edge_id_t t1 = i + drand48() * (numEdges - i);
        if (t1 != i) {
            swap(startVertex[i], startVertex[t1]);
            swap(endVertex[i], endVertex[t1]);
        }
    }
    
    cout << "generate edge weights..." << endl;
    
    weights = (weight_t *)malloc(numEdges * sizeof(weight_t));
    
    for (edge_id_t i = 0; i < numEdges; i++) {
        weights[i] = MinWeight + (weight_t)(MaxWeight - MinWeight) * drand48();
    }
    
    g->weights = weights;
    g->n = TotVertices;
    
    cout << "reconstruct graph..." << endl;
    
    vector< vector<vertex_id_t> > dests(TotVertices);
    vector< vector<weight_t> > weight_vect(TotVertices);
    for (edge_id_t i = 0; i < numEdges; i++) {
        /* direct edge */
        dests[startVertex[i]].push_back(endVertex[i]);
        weight_vect[startVertex[i]].push_back(weights[i]);
        
        /* back edge */
        dests[endVertex[i]].push_back(startVertex[i]);
        weight_vect[endVertex[i]].push_back(weights[i]);
    }
    
    free(startVertex);
    free(endVertex);
    free(weights);
	g->m = 2 * numEdges;
    cout << "m = " << g->m << endl;
    cout.precision(5);
    cout << "average vertex degree = " << fixed << (double)g->m / TotVertices << endl;
    
    /*----------------------------------------------*/
    /*                  write graph                 */
    /*----------------------------------------------*/
    
    cout << "write graph..." << endl;
    
    FILE *F = fopen(outFilename, "wb");
    if (!F) { 
        error(EXIT_FAILURE, 0, "Error in opening file %s", outFilename);
    }
    
    assert(fwrite(&g->n, sizeof(vertex_id_t), 1, F) == 1);
    assert(fwrite(&g->m, sizeof(edge_id_t), 1, F) == 1);
    assert(fwrite(&g->directed, sizeof(bool), 1, F) == 1);
    uint8_t align = 0;
    assert(fwrite(&align, sizeof(uint8_t), 1, F) == 1);
    uint64_t l = 0;
    assert(fwrite(&l, sizeof(edge_id_t), 1, F) == 1);
    for (vertex_id_t i = 0; i < TotVertices; ++i) { 
        l += dests[i].size();
        assert(fwrite(&l, sizeof(edge_id_t), 1, F) == 1);
    }
    
    for (vertex_id_t i = 0; i < TotVertices; ++i) {
        assert(fwrite(&dests[i][0], sizeof(vertex_id_t), dests[i].size(), F) == dests[i].size());
    }
    
    for (vertex_id_t i = 0; i < TotVertices; ++i) {
        assert(fwrite(&weight_vect[i][0], sizeof(weight_t), weight_vect[i].size(), F) == weight_vect[i].size());
    }
    
    fclose(F);
}

int main(int argc, char **argv)
{
    graph_t g;    
    init(argc, argv, &g);
    gen_LFR_graph(&g);
    
    return 0;
}

