#ifndef __CRITICALITY_H__
#define __CRITICALITY_H__

#include "CLLNode.h"
#include "DLLNode.h"

//extern Disk * D;

template <class T>
class Criticality {
	public:
		Criticality();
		void add(Bucket<T> * );
		void inc(Bucket<T> * );
		void remove(Bucket<T> * );
		long getMostCriticalBucket();
		void adjustCriticalities(Bucket<T> *, Bucket<T> *, int );

	private:
		int start; //first Layer list location
};
	
template <class T>
Criticality<T>::Criticality() {

	int i = 0, j = 0; 
	//FILE * file = fopen("CR.bin", "r");
	//D = new Disk ("CR.bin");

	CLLNode<long> * firstLayer = new CLLNode<long>();
	firstLayer->location = D->getFreeDiskBlock(sizeof(CLLNode<long>));
	//---------------------------------
	start = firstLayer->location;
	//---------------------------------

	cout<<"FirstLayerLocation="<<firstLayer->location<<endl;
	for (i = 0; i < MAX_KEYS; i++) {
		DLLNode<long> * secondLayer = new DLLNode<long>();
		firstLayer->keys[i] = secondLayer->location = D->getFreeDiskBlock(sizeof(DLLNode<long>));
		//secondLayer->firstLayerListLoc = firstLayer->location;
		
		//cout<<"L("<<i<<") = "<<firstLayer->keys[i]<<" L_loc="<<secondLayer->firstLayerListLoc<<endl;
		for (j = 0; j < MAX_KEYS; j++) {
			CLLNode<long> * thirdLayer = new CLLNode<long>();
			secondLayer->list[j] = 0;
			secondLayer->keys[j] = thirdLayer->location = D->getFreeDiskBlock(sizeof(CLLNode<long>));
			thirdLayer->secondLayerListLoc = secondLayer->location;
			D->write((char *)thirdLayer, sizeof(CLLNode<long>), thirdLayer->location);
			cout<<"L("<<i<<","<<j<<") = "<<thirdLayer->location<<" Li_loc="<<thirdLayer->secondLayerListLoc<<endl;
		}
		
		D->write((char *)secondLayer, sizeof(DLLNode<long>), secondLayer->location);
	}
	
	D->write((char *)firstLayer, sizeof(CLLNode<long>), firstLayer->location); 
}

template <class T>
void Criticality<T>::add(Bucket<T> * buck) {

	CLLNode<long> * firstLayer = new CLLNode<long>(), * thirdLayer = new CLLNode<long>();
	DLLNode<long> * secondLayer = new DLLNode<long>();
	
	long buckLocation = buck->getLocation();

	D->read((char *)firstLayer, sizeof(CLLNode<long>), start);
	D->read((char *)secondLayer, sizeof(DLLNode<long>), firstLayer->keys[0]);
	D->read((char *)thirdLayer, sizeof(CLLNode<long>), secondLayer->keys[0]);

	//cout<<"start="<<start<<" secondLayerLoc="<<secondLayer->location<<" thirdLayerLoc="<<thirdLayer->location<<endl;
	thirdLayer->insert(buckLocation);	
	buck->ctritListNodeLoc = secondLayer->keys[0];
	buck->l1 = 0;
	buck->l2 = 0;
	D->write((char *)buck, sizeof(Bucket<T>), buck->getLocation());

	secondLayer->list[buck->l2]++;		//this means that we have at least one bucket into this second layer list
	D->write((char *)secondLayer, sizeof(DLLNode<long>), secondLayer->location);

	double fullness = buck->bucketSize/(pow(log2(N),2));
	cout<<"Add criticality for bucket:"<<buckLocation<<" fullness ="<<fullness<<" buck->ctritListNodeLoc="<<buck->ctritListNodeLoc<<endl;
	
}

template <class T>
void Criticality<T>::inc(Bucket<T> * buck) {

	CLLNode<long> * firstLayer = new CLLNode<long>(), * thirdLayer = new CLLNode<long>();
	DLLNode<long> * secondLayer = new DLLNode<long>();

	double k = 2*log2((N/pow(log2(N), 2)));
	double fullness = buck->bucketSize/(pow(log2(N),2));
	
	if ((round(log2(k)) == 1 || buck->l2 < round(log2(k))-1) || (round(log2(k)) > 1 && buck->l1 <= log2(k))) {
		D->read((char *)thirdLayer, sizeof(CLLNode<long>), buck->ctritListNodeLoc);
		thirdLayer->remove(buck->getLocation());
		D->read((char *)secondLayer, sizeof(DLLNode<long>), thirdLayer->secondLayerListLoc);

		secondLayer->list[buck->l2]--;
		cout<<"remove from thirdLayerLoc="<<thirdLayer->location<<endl;
		D->write((char *)secondLayer, sizeof(DLLNode<long>), secondLayer->location);
		
		//remove(buck);
	}

	/*if (round(log2(k)) == 1 ) {
		buck->l2++;
		
		D->read((char *)thirdLayer, sizeof(CLLNode<long>), secondLayer->keys[buck->l2]);
		buck->ctritListNodeLoc = thirdLayer->insert(buck->getLocation());
		
		secondLayer->list[buck->l2]++;
		D->write((char *)secondLayer, sizeof(DLLNode<long>), secondLayer->location);
		//cout<<"[Criticality] secondLayer->list[buck->l2]="<<secondLayer->list[buck->l2]<<endl;
	}
	else*/
	if (round(log2(k)) == 1 || buck->l2 < round(log2(k))-1) {
		buck->l2++;
		
		D->read((char *)thirdLayer, sizeof(CLLNode<long>), secondLayer->keys[buck->l2]);
		cout<<"THIRD_LAYER_LOC: "<<secondLayer->keys[buck->l2]<<endl;
		thirdLayer->insert(buck->getLocation());
		buck->ctritListNodeLoc = thirdLayer->location;

		secondLayer->list[buck->l2]++;
		D->write((char *)secondLayer, sizeof(DLLNode<long>), secondLayer->location);
		D->write((char *)buck, sizeof(Bucket<T>), buck->getLocation());
	}
	else if (round(log2(k)) > 1 && buck->l1 <= log2(k)) {	//the last condition needs because the epoch is not work correct

		//-------D->read((char *)firstLayer, sizeof(CLLNode<long>), secondLayer->firstLayerListLoc);
		//S.O.S
		D->read((char *)firstLayer, sizeof(CLLNode<long>), start);
		//S.O.S
		buck->l1++;
		buck->l2 = 0;

		D->read((char *)secondLayer, sizeof(DLLNode<long>), firstLayer->keys[buck->l1]);
		secondLayer->list[buck->l2]++;
		D->write((char *)secondLayer, sizeof(DLLNode<long>), secondLayer->location);

		D->read((char *)thirdLayer, sizeof(CLLNode<long>), secondLayer->keys[buck->l2]);
		thirdLayer->insert(buck->getLocation());
		buck->ctritListNodeLoc = thirdLayer->location;
		D->write((char *)buck, sizeof(Bucket<T>), buck->getLocation());
	}

	cout<<"[Criticality] BucketLoc="<<buck->getLocation()<<" log2(k)="<<round(log2(k))<<" ("<<buck->l1<<","<<buck->l2<<")"<<endl;	
	cout<<"Inc criticality for bucket:"<<buck->getLocation()<<" fullness ="<<fullness<<" buck->ctritListNodeLoc="<<buck->ctritListNodeLoc<<endl;
	cout<<"secondLayer->list["<<buck->l2<<"]="<<secondLayer->list[buck->l2]<<endl;
}

template <class T>
void Criticality<T>::remove(Bucket<T> * buck) {

	CLLNode<long> * thirdLayer = new CLLNode<long>();
	DLLNode<long> * secondLayer = new DLLNode<long>();

	D->read((char *)thirdLayer, sizeof(CLLNode<long>), buck->ctritListNodeLoc); //to diabazw 2 fores me to inc
	thirdLayer->remove(buck->getLocation());
				
	D->read((char *)secondLayer, sizeof(DLLNode<long>), thirdLayer->secondLayerListLoc);
	secondLayer->list[buck->l2]--;
	cout<<"=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/="<<endl;
	D->write((char *)secondLayer, sizeof(DLLNode<long>), secondLayer->location);
}

template <class T>
long Criticality<T>::getMostCriticalBucket() {

	CLLNode<long> * firstLayer = new CLLNode<long>(), * thirdLayer = new CLLNode<long>();
	DLLNode<long> * secondLayer = new DLLNode<long>();
	int j, i = (int)round(log2(2*log2((N/pow(log2(N), 2)))));

	D->read((char *)firstLayer, sizeof(CLLNode<long>), start);
	D->read((char *)secondLayer, sizeof(DLLNode<long>), firstLayer->keys[i-1]);
	
	for (j = MAX_KEYS; j > 0; j--) {
		if (secondLayer->list[j-1] > 0) {
			D->read((char *)thirdLayer, sizeof(CLLNode<long>), secondLayer->keys[j-1]);
			break;
		}
	}

	cout<<"MostCriticalBucket=("<<i-1<<","<<j-1<<")"<<" criticallBucketLoc="<<thirdLayer->keys[0]<<endl;
	if (j != 0)
		return thirdLayer->keys[0];
	else
		return -1;
}

template <class T>
void Criticality<T>::adjustCriticalities(Bucket<T> * rebalancedBuck, Bucket<T> * newBuck, int operation) {

	if (operation == SPLIT) {
		remove(rebalancedBuck);
		add(rebalancedBuck);
		add(newBuck);
	}	
	else if (operation == FUSE) {
		remove(rebalancedBuck);
		add(newBuck);
	}
	else if (operation == TRANS) {
		remove(rebalancedBuck);
		remove(newBuck);
		add(rebalancedBuck);
		add(newBuck);
	}
}

#endif
