#include <stdio.h>

/* temporary constants for structs 
 * EMPT is used to set bits of a char to 1
 */
#define EMPT 255;

/* struct defenitions & typedef */

typedef struct {
	char name[7];
	unsigned char inode_num;
	} dir_entry;

typedef struct {
	unsigned int block_num;
	unsigned int block_size:24;
	unsigned char block_type;
} inode;

typedef struct {
	unsigned char d0:1;
	unsigned char d1:1;
	unsigned char d2:1;
	unsigned char d3:1;
	unsigned char d4:1;
	unsigned char d5:1;
	unsigned char d6:1;
	unsigned char d7:1;
	} ibit;

typedef union {
	unsigned char data;
	ibit bit;
} bmp_entry;

typedef struct {
	int ri, is, bs, fs, ib;
	bmp_entry * inode_bitmap;
	bmp_entry * block_bitmap;
} superblock;

typedef struct {
	inode * nodes;
	}inode_block;

typedef struct {
	char * data;
	}data_block;

typedef struct {
	dir_entry * dirs;
	}dirblock;

typedef union {
	dirblock entries;
	inode_block node;
	data_block bl_data;
	superblock sblock;
}block;

/* function prototyping */

block * disk_create(int ri, int is, int bs, int fs, int ib);

superblock superblock_create(int ri, int is, int bs, int fs, int ib);
superblock sbi_push(superblock a, int ri, int is, int bs, int fs, int ib);
void superblock_print(superblock a, int fs);
void superblock_copy();

void superblock_get();
void superblock_put();

void print_bmp_entry(bmp_entry *a, int fs);
void dir_put(dir_entry a);

void block_free(int block_num);
void block_put(block a, int inode_number);
block block_get(int inode_number);
void block_print(int block_number);

void inode_free(int inode_number);
inode inode_get(int inode_number);
void inode_put(int inode_number, inode b);

void print_inode(int inode_num);
int free_bmp(bmp_entry *a, int size);

/*   Global Variable Declaration       */

block * global_disk;
superblock global_superblock;

/*
 *     data types + entries
 * dir_entry:	char name[7], char inode_num;
 * inode: 		int block_num, block_size, block_type;
 * ibit: 		unsigned char d0 - d7:1;
 * bmp_entry: 	ibit bit, unsigned char data;
 * superblock: 	int ri, is, bs, fs, ib, bmp_entry * inode_bitmap, bmp_entry * block_bitmap;
 * inode_block:	inode * nodes;
 * data_block:	char * data;
 * block: 		inode_block node, data_block bl_data, superblock sblock;
 */

/* disk_create returns a block pointer
 * for no apparent reason, but "creates"
 * the disk using the given input.  In
 * actuality, it sizes the global variable
 * that represents the partition.
 */

block * disk_create(int ri, int is, int bs, int fs, int ib) {

	int i=0, r=0;
	int b=0;
	int acca, accb, accc;
	superblock a;
	bmp_entry * bmptr;
	inode root_inode;

	i=bs/is;
	r=bs/8;

	a=superblock_create(ri, is, bs, fs, ib);
	global_disk=(block *)malloc(fs);

	/* this section of code creates the dynamic sizes for each of the "block types"
	* in the block union.  it makes sure that the size of the blocks are constant
	* and all portions of the block are accessible by any reference in the union
	*/
	for(b=1; b<=fs; b++) {
		global_disk[b].node.nodes=(inode *)malloc(i);
		global_disk[b].bl_data.data=(char *)malloc(bs);
		global_disk[b].entries.dirs=(dir_entry *)calloc(r);
		}

	global_disk[0].sblock=a;

	/* mounting root inode, creating and putting superblock on partition  */
	superblock_get();
	inode_free(ri);
	block_free(ri);
	superblock_put();

	bmptr=global_disk[0].sblock.inode_bitmap;
	acca=free_bmp(bmptr, global_disk[0].sblock.ib);

	root_inode.block_num=ri;
	root_inode.block_size=1;
	root_inode.block_type='D';
	inode_put(ri, root_inode);

	puts("\tFilesystem successfully created.");
	return 0;
}

/* superblock_create returns a fully
 * created superblock.  its of course
 * used by the disk function to create
 * the global disk's superblock.
 */

superblock superblock_create(int ri, int is, int bs, int fs, int ib) {
	int i=0;
	int c=0, b=0;
	superblock a;

	/* 	initialize inode bitmap size  */

	i=ib*(bs/is);
	a.inode_bitmap=(bmp_entry *)malloc(i);

	/* 	initialize block bitmap size  */
	i=fs-ib-1;
	a.block_bitmap=(bmp_entry *)malloc(i);
	for(b; b<(ib*bs/is); b++)
		a.inode_bitmap[b].data=EMPT;
	b=0;
	for(b; b<(fs-ib-1); b++)
		a.block_bitmap[b].data=EMPT;
	a=sbi_push(a, ri, is, bs, fs, ib);
	
	/*  print superblock information; ri, is, bs, fs, ib, bitmaps.. */
	printf("\tSuperblock Info:\n\t  Inode BMP Size:\t%d\n\t  Block BMP Size:\t%d\n", ib*(bs/is), (fs-ib-1));
	i=(ib*(bs/is))/8 + (fs-ib-1)/8 + 20;
	printf("\t  Size in Bytes:\t%d\n", i);
	/*  return created superblock */
	return a;
}

/* superblock integer push.. abbr. name
 * says it all.
 */

superblock sbi_push(superblock a, int ri, int is, int bs, int fs, int ib) {
	a.ri=ri, a.is=is, a.bs=bs, a.fs=fs, a.ib=ib;
return a;
}

/*
 * this function prints out the contents
 * of a given superblock, given the size
 * of the filesystem.  the filesystem size
 * is needed to be able to print out the
 * bitmaps in the right size.
 */

void superblock_print(superblock a, int fs) {

int ib=global_disk[0].sblock.ib;
int is=global_disk[0].sblock.is;
int bs=global_disk[0].sblock.bs;

	printf("Attempting to print superblock....\n");
	printf("\tri: %d\n\tis: %d\n\tbs: %d\n\tfs: %d\n\tib: %d\n", a.ri, a.is, a.bs, a.fs, a.ib);
	printf("Inode Bitmap Table...\n");
	print_bmp_entry(a.inode_bitmap, ib*(bs/is));
	printf("Block Bitmap Table...\n");
	print_bmp_entry(a.block_bitmap, fs-ib-1);
}

/* this function prints a bitmap, used
 * for error checking.
 */

void print_bmp_entry(bmp_entry * a, int fs) {
int b=fs/8;
int i=0;

	printf("Printing bitmap.\n");
	for(i; i<b; i++) {
	printf("%d", a[i].bit.d0);
	printf("%d", a[i].bit.d1);
	printf("%d", a[i].bit.d2);
	printf("%d", a[i].bit.d3);
	printf("%d", a[i].bit.d4);
	printf("%d", a[i].bit.d5);
	printf("%d", a[i].bit.d6);
	printf("%d\n", a[i].bit.d7);
}

}

/* this function initializes the global
 * modify-able superblock in memory to
 * the value of the partition's superblock
 * It's basically a mount command.
 */

void superblock_get(){
	int i=0;
	int b=0;

	global_superblock.ri=global_disk[0].sblock.ri;
	global_superblock.is=global_disk[0].sblock.is;
	global_superblock.ib=global_disk[0].sblock.ib;
	global_superblock.fs=global_disk[0].sblock.fs;
	global_superblock.bs=global_disk[0].sblock.bs;

	/************* Inode Bitmap Initialization ***************/
	i=global_superblock.ib*(global_superblock.bs/global_superblock.is);
	global_superblock.inode_bitmap=(bmp_entry *)malloc(i);
	for(b; b<i; b++)
		global_superblock.inode_bitmap[b].data=global_disk[0].sblock.inode_bitmap[b].data;

	/************ Block Bitmap Initialization ****************/
	i=global_superblock.fs-global_superblock.ib-1;
	global_superblock.block_bitmap=(bmp_entry *)malloc(i);
	b=0;
	for(b; b<i; b++)
		global_superblock.block_bitmap[b].data=global_disk[0].sblock.block_bitmap[b].data;
}

/* this function initializes the global
 * partition's superblock to the value
 * of the global superblock's.
 * It's basically an unmount command.
 */

void superblock_put(){
	int i=0;
	int b=0;

	global_disk[0].sblock.ri=global_superblock.ri;
	global_disk[0].sblock.is=global_superblock.is;
	global_disk[0].sblock.ib=global_superblock.ib;
	global_disk[0].sblock.fs=global_superblock.fs;
	global_disk[0].sblock.bs=global_superblock.bs;

	/************* Inode Bitmap Initialization ***************/
	i=global_superblock.ib*(global_superblock.bs/global_superblock.is);
	for(b; b<i; b++)
		global_disk[0].sblock.inode_bitmap[b].data=global_superblock.inode_bitmap[b].data;

	/************ Block Bitmap Initialization ****************/
	i=global_superblock.fs-global_superblock.ib-1;
	b=0;
	for(b; b<i; b++)
		global_disk[0].sblock.block_bitmap[b].data=global_superblock.block_bitmap[b].data;
}

/* free_bmp is the generic function to find
 * free space in a given bitmap with a given size.
 * bitmaps and bitmap sizes can be easily
 * calculated from the info in the superblock,
 * and accessed by any function
 */

int free_bmp(bmp_entry *a, int size) {
	int i=1, b=0;

	while(b < size) {
		if     (a[b].bit.d0==1) { return i; }
		else if(a[b].bit.d1==1) { return i+1; }
		else if(a[b].bit.d2==1) { return i+2; }
		else if(a[b].bit.d3==1) { return i+3; }
		else if(a[b].bit.d4==1) { return i+4; }
		else if(a[b].bit.d5==1) { return i+5; }
		else if(a[b].bit.d6==1) { return i+6; }
		else if(a[b].bit.d7==1) { return i+7; }
		b++, i+=8;
	}
	/*  if it ever escapes the while loop, i've exahaustively searched all bitmap entries */
	printf(" ERROR: Escaped while in function free_bmp: no free inodes.\n");
	return 0;
}


/* this function frees a specific inode
 * number in the live version of the
 * inode bitmap.
 */

void inode_free(int inode_number) {
	int i=0, inode_size, mapsize, b=0;
	inode_size=global_superblock.is;
	mapsize=(int)(global_superblock.ib * global_superblock.bs/global_superblock.is);

	/*  this loop finds the correct bitmap byte to look for the passed param */
	while(inode_number > (b+1)*inode_size)
		b++;

	i = inode_number - (b * inode_size) -1;

		if     (i==0) { global_superblock.inode_bitmap[b].bit.d0=0; }
		else if(i==1) { global_superblock.inode_bitmap[b].bit.d1=0; }
		else if(i==2) { global_superblock.inode_bitmap[b].bit.d2=0; }
		else if(i==3) { global_superblock.inode_bitmap[b].bit.d3=0; }
		else if(i==4) { global_superblock.inode_bitmap[b].bit.d4=0; }
		else if(i==5) { global_superblock.inode_bitmap[b].bit.d5=0; }
		else if(i==6) { global_superblock.inode_bitmap[b].bit.d6=0; }
		else if(i==7) { global_superblock.inode_bitmap[b].bit.d7=0; }

}

/* Inode get is supposed to, given an inode number,
 * return the inode data structure corresponding
 * to that number.  Hopefully, it works.
 */

inode inode_get(int inode_number){

	int block=1, r=0, bs=global_disk[0].sblock.bs;
	/*  increments bs by itself to find out what block the inode number is in. */
	while(inode_number > bs) {
		block++;
		bs+=bs;
	}
	return global_disk[block].node.nodes[inode_number];
}

/* Inode number is "IS" variable .. b is the depth
 * into the bitmap that the inode is.. corresponds
 * to the "two dimensional array" position of the
 * inodes in the partition blocks.
 */

void inode_put(int inode_number, inode b){

	int block=1, r=0, bs=global_disk[0].sblock.bs;
	/*  increments bs by itself to find out what block */
	/*  the inode number is in. */
	while(inode_number > bs) { 
		block++;
		bs+=bs;
	}
	global_disk[block].node.nodes[inode_number]=b;
}

/* this function simply prints given inode
 * judging by its block number and inode number;
 */

void print_inode(int inode_num) {
	int i=global_superblock.ri/global_superblock.bs+1;
	printf("Inode %d: size:%d, type:%c\n", global_disk[i].node.nodes[inode_num].block_num,
		global_disk[i].node.nodes[inode_num].block_size, global_disk[i].node.nodes[inode_num].block_type);
}

/* block_free free's an associated block
 * number's position in the block bitmap
 * in the active (memory) superblock
 */

void block_free(int block_number) {
	/* this function is basically a copy of inode_free */
	int i=0, block_size, mapsize, b=0;
	block_size=global_superblock.is;
	mapsize=(int)(global_superblock.ib * global_superblock.bs/global_superblock.is);

	while(block_number > (b+1)*block_size)
		b++;

	i = block_number - (b * block_size) -1;

	if     (i==0) { global_superblock.block_bitmap[b].bit.d0=0; }
	else if(i==1) { global_superblock.block_bitmap[b].bit.d1=0; }
	else if(i==2) { global_superblock.block_bitmap[b].bit.d2=0; }
	else if(i==3) { global_superblock.block_bitmap[b].bit.d3=0; }
	else if(i==4) { global_superblock.block_bitmap[b].bit.d4=0; }
	else if(i==5) { global_superblock.block_bitmap[b].bit.d5=0; }
	else if(i==6) { global_superblock.block_bitmap[b].bit.d6=0; }
	else if(i==7) { global_superblock.block_bitmap[b].bit.d7=0; }

}

/* this function takes block a and the
* associated inode number of said block,
* and puts the block in the associating
* block number. (inode num = data_num)
*/

/*** WARNING!! function block_put is _dangerous_ and should not be used ***/

void block_put(block a, int inode_number) {
	int offset=global_superblock.ib;
	global_disk[offset+inode_number].bl_data=a.bl_data;
}

/* this function returns a block data type
* given an inode number (the inode number
* in this case is the "data_num" value in
* the inode).
*/


block block_get(int block_number){
	int offset=global_superblock.ib;
	return global_disk[offset+block_number];
}

void block_print(int block_number) {
	printf("%s", global_disk[block_number+global_superblock.ib].bl_data.data);
}