/* SSCA2 graph generator */
#include "defs.h"

char outFilename[FNAME_LEN];

/* helper */
void usage(int argc, char **argv)
{
    printf("SSCA2 graph generator\n");
    printf("Usage:\n");
    printf("    %s -s <scale> [options]\n", argv[0]);
    printf("Options:\n");
    printf("   -s <scale>, number of vertices is 2^<scale>\n");
    printf("   -out <filename>, ssca2-<s> is default value\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 (no_file_name) {
        sprintf(outFilename, "ssca2-%d", G->scale);
    }
    
    G->n = (vertex_id_t)1 << G->scale;
}

void gen_SSCA2_graph(graph_t *g)
{
    vertex_id_t TotVertices;
    vertex_id_t *clusterSizes;
    vertex_id_t *firstVsInCluster;
    vertex_id_t estTotClusters, totClusters;
    vertex_id_t *startVertex, *endVertex;
    edge_id_t numEdges;
    weight_t *weights;
    weight_t MinWeight,MaxWeight; 
    vertex_id_t MaxCliqueSize;
    uint32_t MaxParallelEdges = 1;
    double ProbUnidirectional = 1.0;
    double ProbIntercliqueEdges = 0.1;
    vertex_id_t i_cluster, currCluster;
    vertex_id_t *startV, *endV, *d;
    edge_id_t estNumEdges, edgeNum;
    uint32_t i, j, k, t, t1, t2, dsize;
    double p;
    vertex_id_t *permV;
    
    /*----------------------------------------------*/
	/*		            initialize  	           	*/
	/*----------------------------------------------*/
    
	srand48(time(NULL));
    
    MinWeight = g->min_weight;
    MaxWeight = g->max_weight;
    TotVertices = g->n; 
    
	/*----------------------------------------------*/
	/*		         generate clusters	        	*/
	/*----------------------------------------------*/
    
    MaxCliqueSize = 49;
    
  	/* Estimate number of clusters required to make up 
	 * TotVertices and pad by 50% */
	estTotClusters = 1.5 * TotVertices / (MaxCliqueSize / 2);
	/* Allocate a block of memory for this cluster-size array */
	clusterSizes = (vertex_id_t *)malloc(estTotClusters * sizeof(vertex_id_t));
    
	/* Generate random cluster sizes. */
	for (i = 0; i < estTotClusters; i++) {
		clusterSizes[i] = 1 + (drand48() * MaxCliqueSize);
	}
    
	totClusters = 0;
    
	/* Allocate memory for storing the first vertex in each cluster */
	firstVsInCluster = (vertex_id_t *)malloc(estTotClusters * sizeof(vertex_id_t));
    
	/* Compute the first vertex in each cluster */
	firstVsInCluster[0] = 0;
	for (i = 1; i < estTotClusters; i++) {
		firstVsInCluster[i] = firstVsInCluster[i - 1] + clusterSizes[i - 1];
		if (firstVsInCluster[i] > TotVertices - 1) {
			break;
        }
	}
    
	totClusters = i;
	/* Fix the size of the last cluster */
	clusterSizes[totClusters - 1] = TotVertices - firstVsInCluster[totClusters - 1];
    
	/*------------------------------------------------------*/
	/*		        generate intra-cluster edges	       	*/
	/*------------------------------------------------------*/
    
	/* Roughly estimate the total number of edges */
	estNumEdges = (edge_id_t)(((edge_id_t)TotVertices * (double)MaxCliqueSize * (2 - ProbUnidirectional) / 2) +\
		      	    (TotVertices * (double) ProbIntercliqueEdges/(1 - ProbIntercliqueEdges))) * \
				    (1 + MaxParallelEdges / 2);
    
	/* Check if no. of edges is within bounds for 32-bit code */
	edgeNum = 0;
	p = ProbUnidirectional;
	startV = (vertex_id_t *)malloc(estNumEdges * sizeof(vertex_id_t));
	endV = (vertex_id_t *)malloc(estNumEdges * sizeof(vertex_id_t));
	for (i_cluster = 0; i_cluster < totClusters; i_cluster++) {
		for (i = 0; i < clusterSizes[i_cluster]; i++) {
			for (j = 0; j < i; j++) {
				/* generate an edge only in 
				 * one direction */
				for (k = 0; k < 1 + ((uint32_t)(MaxParallelEdges - 1) *  drand48()); k++) {
					startV[edgeNum] = j + firstVsInCluster[i_cluster];	
					endV[edgeNum] = i + firstVsInCluster[i_cluster];
					edgeNum++;
				}
			}
		}
	}
    
	/*----------------------------------------------*/
	/*		       connect the clusters  	    	*/
	/*----------------------------------------------*/
    
	/* generate exponential distances as suggested in the spec */
	dsize = (uint32_t)(log((double)TotVertices) / log(2));
	d = (vertex_id_t *)malloc(dsize * sizeof(vertex_id_t));
	for (i = 0; i < dsize; i++) {
		d[i] = (uint32_t)pow(2, (double)i);
	}
    
	currCluster = 0;
    
	for (i = 0; i < TotVertices; i++) {
		p = ProbIntercliqueEdges;	
		/* determine current cluster */
		for (j = currCluster; j < totClusters; j++) {
			if ((i >= firstVsInCluster[j]) && (i < firstVsInCluster[j] + clusterSizes[j])) {
				currCluster = j;
				break;
			}
		}
        
		for (t = 1; t < dsize; t++) {
			j = (i + d[t] + (uint32_t)(drand48() * (d[t] - d[t - 1]))) % TotVertices;
			/* Ensure that i and j don't belong to the same cluster */
			if ((j < firstVsInCluster[currCluster]) || \
			    (j >= firstVsInCluster[currCluster] + clusterSizes[currCluster])) {
				
				for (k = 0; k < 1 + ((uint32_t)(MaxParallelEdges - 1) * drand48()); k++) {
					if (p > drand48()) {
						startV[edgeNum] = i;
						endV[edgeNum] = j;
						edgeNum++;	
					}
				}
			}
			p = p / 2;
		}
	}
	
	numEdges = edgeNum;
    
	free(clusterSizes);  
	free(firstVsInCluster);
	free(d);
    
	/*--------------------------------------------------------------*/
	/*		        shuffle vertices to remove locality     		*/
	/*--------------------------------------------------------------*/
    
	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(i = 0; i < TotVertices; i++) {
		permV[i] = i;
	}
    
	/* Permute the vertices and store them in permV */
	for (i = 0; i < TotVertices; i++) {
		t1 = i + drand48() * (TotVertices - i);
		if (t1 != i) {
			t2 = permV[t1];
			permV[t1] = permV[i];
			permV[i] = t2;
		}
	}
	
	for (i = 0; i < numEdges; i++) {
		startVertex[i] = permV[startV[i]];
		endVertex[i] = permV[endV[i]];
	}
	
	free(startV);
	free(endV);
	free(permV);
    
	/*----------------------------------------------*/
    /*              generate edge weights           */
    /*----------------------------------------------*/
    
    weights = (weight_t *)malloc(numEdges * sizeof(weight_t));
    for (i = 0; i < numEdges; i++) {
        weights[i] = MinWeight + (weight_t)(MaxWeight - MinWeight) * drand48();
    }
    
	g->weights = weights;
	g->n = TotVertices;
    
    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;
    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_SSCA2_graph(&g);
    
    return 0;
}

