#include <LINK/algorithm/Partitioning.h>
#include <LINK/graph/Attribute.h>
#include <LINK/graph/Vertex.h>
#include <LINK/graph/Graph.h>
#include <LINK/graph/Edge.h>
#include <LINK/basic/Set.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
//#include <sys/time.h>		commented out for portability
//#include <sys/resource.h>
static BucketArray* Buckets[2];
static CutStatePtr cutstate;	/* defined global for debugging             */
static int 	NumPosBuckets;	/* maxdegree 				    */
static int 	NumVertices;	/* to avoid repeated calls to order()       */
static int 	BestCost;	/* #cuts in best partition so far           */
static int 	BestPartSizes[2];/*#verts in each partition (in best so far)*/
static double 	BestQuot;	/* best quotient cut found so far           */
static double 	BestRatio;	/* best ratio cut found so far              */
static int 	*BestLocations;	/* saves the entire best partitioning       */
int maxcutDetermineSide(int max_0, int max_1, int current_cost, 
		      int * part_sizes)
{
	int new_cost0, new_cost1;
        new_cost0 = current_cost + max_0;
        new_cost1 = current_cost + max_1;
	if (new_cost0 > new_cost1)
                return 0;
        else if (new_cost1 > new_cost0)
                return 1;
	else if (part_sizes[0] > part_sizes[1])
		return 0;
	else 
		return 1;
}
int quotDetermineSide(int min_0, int min_1, int current_cost, 
		      int * part_sizes)
{
	int temp, new_cost0, new_cost1, new_size0, new_size1;
	double new_quot0, new_quot1;
        if ((min_0 <= NumPosBuckets) && (min_1 <= NumPosBuckets)) {
                new_cost0 = current_cost + min_0;
                new_cost1 = current_cost + min_1;
                new_size0 = MIN(part_sizes[0]-1, part_sizes[1]+1);
                new_size1 = MIN(part_sizes[1]-1, part_sizes[0]+1);
                if (new_size0 > 0)
                        new_quot0= ((double) new_cost0 / (double) new_size0);
                else
                        new_quot0 = MAX_INT;
                if (new_size1 > 0)
                        new_quot1 = ((double) new_cost1 / (double) new_size1);
                else
                        new_quot1 = MAX_INT;
                if (new_quot0 < new_quot1)
                        temp = 0;
                else
                        temp = 1;
	} else if (min_0 <= NumPosBuckets) {
                        temp = 0;
        } else if (min_1 <= NumPosBuckets) {
                        temp = 1;
        } else       printf("error: quotDetermineSide(): empty Buckets\n");
	return temp;
}
int ratioDetermineSide(int min_0, int min_1, int current_cost, 
		       int * part_sizes)
{
	int temp, new_cost0, new_cost1, new_size0, new_size1;
	double new_ratio0, new_ratio1;
        if ((min_0 <= NumPosBuckets) && (min_1 <= NumPosBuckets)) {
                new_cost0 = current_cost + min_0;
                new_cost1 = current_cost + min_1;
                new_size0 = (part_sizes[0]-1) * (part_sizes[1]+1);
                new_size1 = (part_sizes[1]-1) * (part_sizes[0]+1);
                if (new_size0 > 0)
                        new_ratio0 = ((double) new_cost0 / (double) new_size0);
                else
                        new_ratio1 = MAX_INT;
                if (new_size1 > 0)
                        new_ratio1 = ((double) new_cost1 / (double) new_size1);
                else
                        new_ratio1 = MAX_INT;
                if (new_ratio0 < new_ratio1)
                        temp = 0;
                else
                        temp = 1;
	} else if (min_0 <= NumPosBuckets) {
                        temp = 0;
        } else if (min_1 <= NumPosBuckets) {
                        temp = 1;
        } else       printf("error: ratioDetermineSide(): empty Buckets\n");
	return temp;
}
void decrementGain(VertexInfoPtr vptr)
{
	int loc = vptr->loc;
	ContainerNode *cn = Buckets[loc]->remove(vptr);
	vptr->cost--;
	vptr->lp = Buckets[loc]->insert(vptr->cost, cn);
}
void incrementGain(VertexInfoPtr vptr)
{
	int loc = vptr->loc;
	ContainerNode *cn = Buckets[loc]->remove(vptr);
	vptr->cost++;
	vptr->lp = Buckets[loc]->insert(vptr->cost, cn);
}
void incrementGainsFree(Edge *edge)
{
	VertexInfoPtr vptr;
	Vertex * vertex;
	Iterator<Vertex*> get_vertex(edge->vertices());
	while (get_vertex(vertex)) {
		getAttribute(go(vertex), "v_info", vptr);
		if (!vptr->lock)
			incrementGain(vptr);
	}
}
void decrementGainsFree(Edge *edge)
{
	VertexInfoPtr vptr;
	Vertex * vertex;
	Iterator<Vertex*> get_vertex(edge->vertices());
	while (get_vertex(vertex)) {
		getAttribute(go(vertex), "v_info", vptr);
		if (!vptr->lock)
			decrementGain(vptr);
	}
}
void decrementGainFreeFrom(Edge *edge, int from_block)
{
	VertexInfoPtr vptr;
	Vertex * vertex;
	Iterator<Vertex*> get_vertex(edge->vertices());
	while (get_vertex(vertex)) {
		getAttribute(go(vertex), "v_info", vptr);
		if (!vptr->lock && vptr->loc == from_block) {
			decrementGain(vptr);
			return;
		}
	}
}
void incrementGainFreeTo(Edge *edge, int from_block)
{
	VertexInfoPtr vptr;
	Vertex * vertex;
	Iterator<Vertex*> get_vertex(edge->vertices());
	while (get_vertex(vertex)) {
		getAttribute(go(vertex), "v_info", vptr);
		if (!vptr->lock && vptr->loc != from_block) {
			incrementGain(vptr);
			return;
		}
	}
}
VertexInfoPtr KLFMMoveVertex(ContainerNode *cn, int *part_sizes,
				int &current_cost)
{
	CutStatePtr cptr;
	Edge * edge;
	Set<Edge*> incident_edges;
	VertexInfoPtr vptr = * (VertexInfoPtr*) cn->info();
	int from_block;
	vptr->lock = 1;
	from_block = vptr->loc;
	vptr->loc = !vptr->loc;
	incident_edges = vptr->v->incidentEdges();
	Iterator<Edge*> get_edge(&incident_edges);
	while (get_edge(edge)) {
		getAttribute(go(edge), "cutstate", cptr);
		if ((*cptr)[LOCKED][!from_block] == 0) {
			if ((*cptr)[FREE][!from_block] == 0) 
				decrementGainsFree(edge);
			else if ((*cptr)[FREE][!from_block] == 1) 
				incrementGainFreeTo(edge,from_block);
		}
		(*cptr)[FREE][from_block]--;	
		(*cptr)[LOCKED][!from_block]++;	
		if ((*cptr)[LOCKED][from_block] == 0) {
			if ((*cptr)[FREE][from_block] == 0)
				incrementGainsFree(edge);
			else if ((*cptr)[FREE][from_block] == 1)
				decrementGainFreeFrom(edge, from_block);
		}
	}
	part_sizes[from_block]--;
	part_sizes[!from_block]++;
	current_cost += vptr->cost;
	return vptr;
}
		
void saveBest(VertexInfoPtr cells, int current_cost, int *part_sizes)
{
	int i;
	BestCost = current_cost;
	BestPartSizes[0] = part_sizes[0];
	BestPartSizes[1] = part_sizes[1];
	for (i=0; i<NumVertices; i++)
		BestLocations[i] = cells[i].loc;
}
int maxcutSaveIntermediate(VertexInfoPtr cells, int current_cost,
			   int *part_sizes, int &temp_best_cost, 
			   int *temp_best_part_sizes)
{
	if (current_cost >= temp_best_cost) {
		temp_best_cost = current_cost;
		temp_best_part_sizes[0] = part_sizes[0];
		temp_best_part_sizes[1] = part_sizes[1];
		return TRUE;
	}
	return FALSE;
}
int quotSaveIntermediate(VertexInfoPtr cells, int current_cost,int *part_sizes,
			 int &temp_best_cost, int *temp_best_part_sizes)
{
	int current_min_size = MIN(part_sizes[0], part_sizes[1]);
	double current_quot = (double) current_cost / current_min_size;
	int temp_best_min_size = MIN(temp_best_part_sizes[0], 
				     temp_best_part_sizes[1]);
	double temp_best_quot = (double) temp_best_cost / temp_best_min_size;
	if (current_quot <= temp_best_quot) {
		temp_best_cost = current_cost;
		temp_best_part_sizes[0] = part_sizes[0];
		temp_best_part_sizes[1] = part_sizes[1];
		return TRUE;
	}
	return FALSE;
}
int ratioSaveIntermediate(VertexInfoPtr cells,int current_cost,int *part_sizes,
                         int &temp_best_cost, int *temp_best_part_sizes)
{
	int current_denom = part_sizes[0] * part_sizes[1];
	double current_ratio = (double) current_cost / current_denom;
	int temp_best_denum = temp_best_part_sizes[0] * temp_best_part_sizes[1];
	double temp_best_ratio = (double) temp_best_cost / temp_best_denum;
	if (current_ratio <= temp_best_ratio) {
		temp_best_cost = current_cost;
		temp_best_part_sizes[0] = part_sizes[0];
		temp_best_part_sizes[1] = part_sizes[1];
		return TRUE;
	}
	return FALSE;
}
int maxcutSaveIfBetter(VertexInfoPtr cells, int current_cost, 
		      int *part_sizes)
{
	if (current_cost > BestCost) {
		BestCost = current_cost;
		saveBest(cells, current_cost, part_sizes);
		return TRUE;
	}
	return FALSE;
}
int quotSaveIfBetter(VertexInfoPtr cells, int current_cost, 
		      int *part_sizes)
{
	int min_size = MIN(part_sizes[0], part_sizes[1]);
	double current_quot;
	if (min_size > 0)
		current_quot = (double) current_cost / min_size;
	else
		current_quot = MAX_INT;
	if (current_quot < BestQuot) {
		BestQuot = current_quot;
		saveBest(cells, current_cost, part_sizes);
		return TRUE;
	}
	return FALSE;
}
int ratioSaveIfBetter(VertexInfoPtr cells, int current_cost, 
			int *part_sizes)
{
	int denom = part_sizes[0] * part_sizes[1];
	double current_ratio;
	if (denom > 0)
		current_ratio = (double) current_cost / denom;
	else
		current_ratio = MAX_INT;
	if (current_ratio < BestRatio) {
		BestRatio = current_ratio;
		saveBest(cells, current_cost, part_sizes);
		return TRUE;
	}
	return FALSE;
}
void initKLFM(Graph *g, int prob_type, VertexInfoPtr cells, CutStatePtr cptr)
{
	int num_buckets, part_sizes[2];
	int i;
	NumPosBuckets = g->maxDegree();
	num_buckets = 2*NumPosBuckets+1;
	BucketArray * A_Buckets = new BucketArray(num_buckets);
	BucketArray * B_Buckets = new BucketArray(num_buckets);
	Buckets[0] = A_Buckets;
	Buckets[1] = B_Buckets;
	initCells(g, cells);
	Edge *edge;
        const MSet<Edge*>& edges = g->edges();
	Iterator<Edge*> get_edge(&edges);      
	i=0;
	while (get_edge(edge)) {
		setAttribute(go(edge), "cutstate", &cptr[i]);
		i++;
	}
	BestQuot = 10.0;
	BestRatio = 10.0;
	if (prob_type == MAX_CUT)
		BestCost = 0;
	else
		BestCost = g->size();
	BestPartSizes[0] = BestPartSizes[1] = 0;
	for (i=0; i<NumVertices; i++) 
		BestLocations[i] = 0;
}
void KLFMpass(Graph *g,int alg_type, int&current_cost, int *part_sizes,
	      VertexInfoPtr* rollback, VertexInfoPtr cells, CutStatePtr cptr)
{
	int i,j;
	int side, more_work = TRUE;
	ContainerNode *cn;
	int roll, min_0, min_1;
	VertexInfoPtr vptr;
	int best_cost;
	int best_part_sizes[] = {0, 0};
	int  (*determineSide)(int,int,int,int*);
	int  (*saveIntermediate)(VertexInfoPtr, int,int*,int&, int*);
	int  (*saveIfBetter)(VertexInfoPtr, int , int*);
	int (BucketArray::*findNext)();
	ContainerNode * (BucketArray::*extractNext)();
	findNext = &BucketArray::findMin;
	extractNext = &BucketArray::extractMin;
	best_cost = MAX_INT;
	if (alg_type == MIN_QUOTIENT) {
		determineSide = 	quotDetermineSide;
		saveIntermediate =  	quotSaveIntermediate;
		saveIfBetter =  	quotSaveIfBetter;
	} else if (alg_type == MIN_RATIO) {
		determineSide = 	ratioDetermineSide;
		saveIntermediate =  	ratioSaveIntermediate;
		saveIfBetter =  	ratioSaveIfBetter;
	} else if (alg_type == MAX_CUT) {
		determineSide = 	maxcutDetermineSide;
		saveIntermediate =  	maxcutSaveIntermediate;
		saveIfBetter =  	maxcutSaveIfBetter;
		findNext = 		&BucketArray::findMax;
		extractNext = 		&BucketArray::extractMax;
		best_cost = 		0;
	} else
		printf("KLFMpass: error, illegal problem type\n");
	while (more_work) {
		roll = 0;
		initNetInfo(g, Buckets, cptr, cells, current_cost, part_sizes);
		for (i=0; i<NumVertices; i++) {
			min_0 = (Buckets[0]->*findNext)();
			min_1 = (Buckets[1]->*findNext)();
			side = determineSide(min_0, min_1,
						current_cost, part_sizes);
			cn = (Buckets[side]->*extractNext)();
			vptr = KLFMMoveVertex(cn, part_sizes, current_cost);
			rollback[roll++] = vptr;
			if (saveIntermediate(cells,current_cost,part_sizes,
					      best_cost, best_part_sizes))
				roll = 0;
		}
		for (j=0; j<roll; j++) {
			vptr = rollback[j];
			vptr->loc = !vptr->loc;
		}
		if (!saveIfBetter(cells, best_cost, best_part_sizes))
			more_work = FALSE;
	}
}
int KLFM(Graph *g, int problem_type, int iterations)
{
	Attribute<VertexInfoPtr> v_info(g, "v_info", (VertexInfoPtr) NULL);
	Attribute<CutStatePtr> cs(g, "cutstate", (CutStatePtr) NULL);
	NumVertices 	= g->order();
	int num_nets 	= g->size();
	VertexInfoPtr *rollback = new VertexInfoPtr[NumVertices];
	VertexInfoPtr cells = new VertexInfo[NumVertices];
	BestLocations = new int[NumVertices];
	cutstate = new CutState[num_nets];//defined global for debugging
	int i, more_passes = TRUE;
	int current_cost, part_sizes[2];
	double time_;
	initKLFM(g, problem_type, cells, cutstate);
	// time_ = log_time();
	//while (more_passes) 
	while (iterations-- > 0) {
		randomizeVertices(cells, NumVertices);
		KLFMpass(g, problem_type, current_cost, part_sizes, rollback,
				cells, cutstate);
		// time_ += log_time();
		//if (time_ >= time_to_run)
		//	more_passes = FALSE;
		
	}
	//********Check solution for correctness
	for (i=0; i<NumVertices; i++)
		cells[i].loc = BestLocations[i];
	initNetInfo(g, Buckets, cutstate, cells, current_cost, part_sizes);
	if ((BestCost != current_cost) || 
		(part_sizes[0] != BestPartSizes[0]) ||
		(part_sizes[1] != BestPartSizes[1]))
		cout << "KLFM: error - incorrect partitioning" << endl;
	for (i=0; i<NumVertices; i++) {
		Vertex *v = cells[i].v;
		setAttribute(go(v), "partition", cells[i].loc);
	}
	delete Buckets[0];
	delete Buckets[1];
	delete [] rollback;
	delete [] cells;
	delete [] cutstate;
	return BestCost;
}
