
#ifndef HIERARCHY_H
#define HIERARCHY_H

#include <math.h>

#define NDATAPERGROUP 1024
#define BUFFERSIZE 1024
#define NPREGROUPS 1024
#define MAXLEVELS 10
#define MAXDIMS 64

float buffer[MAXLEVELS][NPREGROUPS][BUFFERSIZE];
int nentries[MAXLEVELS][NPREGROUPS];
int totalentries[MAXLEVELS][NPREGROUPS];


class Point {
public:
	int ndims;
	int category;
	float *coords;
	Point() : ndims(0), coords(NULL) {};
	void setdims(int n) { ndims = n; coords = new float[n]; };
};

class Group {
public:
	float *startingcoords;
	int *catcoords;
	int *numericalnulls;
	int *categoricalnulls;
	float *parcoords;
	float size;
	int nchildren;
	Group **children;
	char pfilename[64];
	Group *neighbors;
	int numneighbors;
	int category; // for testing classification
	
	static int ndims;
	static int ncatdims;

	Group() : nchildren(0), numneighbors(0), category(-1) {};


	void addChild(Group *gptr) { 
		Group **temp;
		int i;
		temp = children;
		children = new Group *[nchildren+1];
		for (i=0;i<nchildren;i++) {
			children[i] = temp[i];
		}
		children[nchildren] = gptr;
		if (nchildren>0) delete [] temp;
		nchildren++;
	};

	void SetDims(int n) {
		int i;
		ndims = n;
		startingcoords = new float[ndims];
		numericalnulls = new int[ndims];
		for (i=0;i<ndims;i++) {
			startingcoords[i] = 0.0;
			numericalnulls[i] = 0;
		}
		size = 1.0;
	};

	void SetCatDims(int n) {
		int i;
		ncatdims = n;
		catcoords = new int[ncatdims];
		parcoords = new float[ncatdims];
		categoricalnulls = new int[ncatdims];
		for (i=0;i<ncatdims;i++) {
			catcoords[i] = 0;
			parcoords[i] = 0.0;
			categoricalnulls[i] = 0;
		}
		size = 1.0;
	};

};

class PreGroup : public Group {
public:
	int k; // number of partitions per dimension
	float partition;
	int maxnentries;
	int level;
	static int nextfilenumber;

	PreGroup() : Group() , level(0) {};
	
	void Init() {
		int n;
		for(n=0;n<NPREGROUPS;n++) { 
			nentries[level][n] = 0; 
			totalentries[level][n] = 0; 
		}
		startingcoords = new float[ndims];
		for (n=0;n<ndims;n++) {
			startingcoords[n] = 0.0;
		}
		size = 1.0;
	};

	void FindK() {
		for (k=0;pow(k,ndims)<NPREGROUPS;k++) {};
		k--;
		partition = 1.0/(float)k;
		maxnentries = BUFFERSIZE/ndims;
	};

	void Insert(float *entry) { 
		int n, i;
		FILE *fptr;
		char fname[64];
		int ind = 0;
		int currentn = 0;
		for (n=0;n<ndims;n++) {
			ind += k*n+(int)(entry[n]/partition);
		}
		for (i=0;i<ndims;i++) {
			buffer[level][ind][nentries[level][ind]*ndims+i] = entry[i];
		}
		nentries[level][ind]++;
		totalentries[level][ind]++;
		// when buffer overflows, write to file
		if (nentries[level][ind]>=maxnentries) {
			sprintf(fname,"%d-%d.entries", level, ind);
			fptr = fopen(fname,"r+b");
			if (fptr!=NULL) {
				fread(&currentn,sizeof(int),1,fptr);
			} else {
				fptr = fopen(fname,"wb");
			}
			currentn += maxnentries;
			fwrite(&currentn,sizeof(int),1,fptr);
			fseek(fptr,0,SEEK_END);
			fwrite(buffer[level][0],sizeof(float),maxnentries*ndims,fptr);
			fclose(fptr);
			nentries[level][ind] = 0;
		}
	};
	
	Group *BuildTree() { 
		int i,j,n;
		Group *gptr;
		PreGroup temp;
		FILE *fptr;
		FILE *fptr2;
		char fname[64];
		int ninfile;
		float entry[MAXDIMS];
		temp.level = level + 1;
		gptr = new Group();
		sprintf(fname,"p%d.entries", nextfilenumber);
		nextfilenumber++;
		// for each cell
		for (i=0;i<NPREGROUPS;i++) {
			if ((temp.level<MAXLEVELS)&&(totalentries[level][i]>=maxnentries)) {
				// subdivide this cell
				temp.Init();
				// set coords
				for (j=0;j<ndims;j++) {
					n = i/(int)(pow(k,j));
					n = n%k;
					startingcoords[j] = partition * (float)n;
				}
				temp.size = size/(float)k;
				// read from temp file and insert into temp
				sprintf(fname,"%d-%d.entries", level, i);
				fptr = fopen(fname,"rb");
				fread(&ninfile,sizeof(int),1,fptr);
				for (j=0;j<ninfile;j++) {
					fread(entry,sizeof(float),ndims,fptr);
					temp.Insert(entry);
				}
				fclose(fptr);
				// insert those in memory into temp
				for (j=0;j<nentries[level][i];j++) {
					temp.Insert(&(buffer[level][i][ndims*i]));
				}
				// call temp to build tree
				gptr->addChild(temp.BuildTree());
			} else {
				// just write to (permanent) file
				fptr2 = fopen(fname,"r+b");
				if (fptr2==NULL) {
					fptr2 = fopen(fname,"wb");
				} else {
					fseek(fptr2,0,SEEK_END);
				}
				strcpy(gptr->pfilename, fname);
				if (totalentries[level][i]>=maxnentries) { // write from file
					sprintf(fname,"%d-%d.entries", level, i);
					fptr = fopen(fname,"rb");
					fread(&ninfile,sizeof(int),1,fptr);
					for (j=0;j<ninfile;j++) {
						fread(entry,sizeof(float),ndims,fptr);
						fwrite(entry,sizeof(float),ndims,fptr2);
					}
					fclose(fptr);
				}
				// write from memory
				for (j=0;j<nentries[level][i];j++) {
					fwrite(&(buffer[level][i][ndims*i]),sizeof(float),ndims,fptr2);
				}
				fclose(fptr2);	
			}
		}
		// return a Group (not a PreGroup) to form a connected tree
		return gptr;
	};

};

class Hierarchy {
public:
	int ndims;
	int nentries;
	Group *rootptr;
	PreGroup preroot;

	Hierarchy() {};

	void ReadData(char *fname) {
		FILE *fptr;
		int n;
		float buffer[MAXDIMS];
		fptr = fopen(fname,"rb");
		fread(&ndims,sizeof(int),1,fptr);
		Group::ndims = ndims;
		preroot.FindK();
		preroot.Init();
		fread(&nentries,sizeof(int),1,fptr);
		for (n=0;n<nentries;n++) {
			fread(buffer,sizeof(float),ndims,fptr);
			preroot.Insert(buffer);
		}
		rootptr = preroot.BuildTree();
	};


};

int Group::ndims = 0;
int Group::ncatdims = 0;
int PreGroup::nextfilenumber = 0;

float GetDistance(const Point &p, const Group &g) {

	int i;
	float d;
	d = 0.0;
	for (i=0;i<Group::ndims;i++) {
		d += (p.coords[i] - g.startingcoords[i]) * (p.coords[i] - g.startingcoords[i]);
	}
	return d;

};

#endif

