//CSU 480 File System Project
//2/17/06
//Ken Eimer, Brandon Schory, Brian Tobia
//filesystem.cc

#include "filesystem.h"
#include <string.h>

//Constructor
FileSystem::FileSystem( Disk* _disk, int _blocksize, bool isnew ) {
  disk = _disk;
  blocksize = _blocksize;
  maxfileblocks = blocksize/sizeof(int) - 1;
  openFile = false;
  fileOffset = -1;
  editBlock = -1;
  fileIndexBlock = -1;
  

  if ( isnew ) {
    // call AllocateBlock to make sure that block 0 is reserved for directory
    disk->AllocateBlock();

    // write nextblock=-1 into block 0.
     char* directoryblock = new char[blocksize];
    DirectoryBlock* p = (DirectoryBlock*)directoryblock;
    p->nextBlockNumber = -1;
    p->numEntriesInBlock = 0;
    disk->WriteBlock( 0, directoryblock );
    delete directoryblock;

    numentries = 0;
 }
  else {
     // sequentially go through all directory blocks and read the entries.
    char* directoryblock = new char[blocksize];
    DirectoryBlock* p = (DirectoryBlock*)directoryblock;
    int blocknumber = 0;  // start with block 0.

    numentries = 0;

    while ( blocknumber != -1 ) {
      // load the block in memory
      disk->ReadBlock( blocknumber, directoryblock );

      // read new nextblock and numentries
      blocknumber = p->nextBlockNumber;

      for ( int i=0; i<p->numEntriesInBlock; i++ ) {
	memcpy( (void*)&entries[numentries], (void*)( p->entriesInBlock + i), sizeof(Entry));
	numentries ++;
      }
    }
    delete directoryblock;
 }
}

   
// Destructor needs to write the directory to disk.
// For simplicity let's free all directory blocks except block 0, and then generate from scratch.
FileSystem::~FileSystem() {
  // load block 0
  char* blockzero = new char[blocksize];
  DirectoryBlock* p0 = (DirectoryBlock*) blockzero;
  disk->ReadBlock( 0, blockzero );

  // free all directory blocks except block 0
  int currentBlockNumber = p0->nextBlockNumber;
  p0->nextBlockNumber = -1;
  disk->WriteBlock( 0, blockzero );

  while ( currentBlockNumber != -1 ) {
    char* currentblock = new char[blocksize];
    DirectoryBlock* p = (DirectoryBlock*) currentblock;
    disk->ReadBlock( currentBlockNumber, currentblock );
    int previousBlockNumber = currentBlockNumber;
    currentBlockNumber = p->nextBlockNumber;
    disk->FreeBlock( previousBlockNumber );
    delete currentblock;
  }

  // write the directory to disk
  // Before the while loop, assumes p0 points to a memory buffer for a directory block which is not filled yet.

  int blocknumber = 0;
  int numFinishedEntries = 0;
  int maxNumEntriesInBlock = (blocksize-sizeof(int)-sizeof(int)) / sizeof(Entry);

  while ( numentries - numFinishedEntries > maxNumEntriesInBlock ) {
    p0->nextBlockNumber = disk->AllocateBlock();
    p0->numEntriesInBlock = maxNumEntriesInBlock;
    for ( int i = 0; i < p0->numEntriesInBlock; i++ ) {
      memcpy( (void*)(p0->entriesInBlock+i), (void*)(entries+i+numFinishedEntries), sizeof(Entry) );
    }
    disk->WriteBlock( blocknumber, (char*)p0 );
    blocknumber = p0->nextBlockNumber;
    numFinishedEntries += maxNumEntriesInBlock;
  }

  p0->nextBlockNumber = -1;
  p0->numEntriesInBlock = numentries - numFinishedEntries;
  for ( int i=0; i<p0->numEntriesInBlock; i++ ) {
    memcpy( (void*)(p0->entriesInBlock+i), (void*)(entries+i+numFinishedEntries), sizeof(Entry) );
  }
  disk->WriteBlock( blocknumber, (char*)p0 );

  delete blockzero;
}


//ls function: lists all files currently in the filesystem
void FileSystem::ls() {
  for ( int i=0; i<numentries; i++ ) {
    printf( "%s  ", entries[i].filename );
  }
  printf( "\n" );
}


//create function: creates a new file in the filesystem with the name given in filename
void FileSystem::create( char* filename ) {

  //Stores the length of the inputted file name
  int len = strlen(filename);

  //Set the name length to be at most the max name length allowd
  if(len > MAXNAMELEN){
	len = MAXNAMELEN;
  }

  //char array to store the filename
  char * fname = new char[len];

  //Error case: filename begins with a space (all other file names acceptable)
  if(filename[0] == ' '){
	printf("Invalid file name.\n");
	return;
  }

  //Genereate a copy of the user defined filename that is truncated to the proper length
  for(int i = 0; i < len; i++){
	fname[i] = filename[i];
  }

  //Null terminate the string
  fname[len] = 0;

  //Stores the blocknumber of any file in the system of the same name
  int blocknumber = -1;

  //Compare (using strcmp) the fname with other files in the filesytem to determine if a file by that name already exists
  for ( int j=0; j<numentries; j++){
	if (strcmp(entries[j].filename, fname) == 0 && (strlen(entries[j].filename) == strlen(fname))){
            blocknumber = entries[j].indexBlockNumber;
	}
  }

  //Error case: filename already exists
  if(blocknumber != -1){
	printf("File \"%s\" already exists.\n", fname);
	return;
  }

  //Error case: maximum number of fils has already been created
  if ( numentries >= MAXNUMFILES ) {
    printf( "Can't create a new file for the number of files reaches limit.\n" );
    return;
  }

  //Copy filename into entries
  strcpy( entries[numentries].filename, fname );

  //Allocate a new block for the file's index file
  entries[numentries].indexBlockNumber = disk->AllocateBlock();

  //Create the file index
  char* fIndex = new char[blocksize];
  FileIndex* p = (FileIndex*)fIndex;
  
  //Set the initial file size
  p->fileSize = 1;

  //Allocate the first data block for the file
  int x = disk->AllocateBlock();
  p->blocks[0] = x;

  //Generate a string of length 0 to be stored as the initial file data
  char* tmp = new char[blocksize];
  tmp[0] = 0;

  //Write the first data block of the new file
  disk->WriteBlock(x, tmp);

  //Write the file's index block
  disk->WriteBlock(entries[numentries].indexBlockNumber, fIndex);

  //Clean up memory
  delete fIndex;
  delete tmp;

  //Increase the total file count
  numentries ++;

  //Print user alert
  printf( "File created.\n");

}


//rm function: removes a file (tfilename) from the filesystem
void FileSystem::rm( char* tfilename ) {

  //Stores the blocknumber of any file in the system of the same name
  int blocknumber = -1;

  //Stores the number of the entry for any file in the system of the same name
  int entry = -1;

  //Compare (using strcmp) the tfilename with other files in the filesytem to determine if a file by that name already exists
  for ( int j=0; j<numentries; j++){
	if (strcmp(entries[j].filename, tfilename) == 0 && (strlen(entries[j].filename) == strlen(tfilename))){
            blocknumber = entries[j].indexBlockNumber;
            entry = j;
	}
  }

  //Error case: file does not exists
  if(blocknumber == -1){
	printf("File does not exist\n");
	return;
  }
  
  //Error case: file is currently open
  if(blocknumber == fileIndexBlock){
	printf("Cannot delete, file open.\n");
	return;
  }
  
  //Remove the file entry form entries and shift the entries to fill the gap
  for(int i = entry + 1 ; i < numentries; i++){
       entries[i-1] = entries[i];
  }
  
  //Decrement the counter for the number of entries
  numentries--;

  //Read the file's index block in to memory
  char* fIndex = new char[blocksize];
  FileIndex* p = (FileIndex*)fIndex;
  disk->ReadBlock( blocknumber, fIndex );
    
  //Deallocate the files data blocks
  for (int i=0; i<p->fileSize; i++){
	disk->FreeBlock(p->blocks[i]);
  }

  //Clean up memory
  delete fIndex;

  //Print user alert
  printf("%s deleted\n", tfilename); 

}


//open function: opens a file (tfilename) for the user to perform operations on
void FileSystem::open( char* tfilename ) {

  //Error case: cannot open a new file if one is already open
  if (openFile){
        printf("A file is already open\n");
        return;
  }
    
  //Stores the blocknumber of any file in the system of the same name
  int blocknumber = -1;

  //Compare (using strcmp) the tfilename with other files in the filesytem to determine if a file by that name already exists
  for ( int j=0; j<numentries; j++){
	if (strcmp(entries[j].filename, tfilename) == 0 && (strlen(entries[j].filename) == strlen(tfilename))){
            blocknumber = entries[j].indexBlockNumber;
	}
    }

  //Error case: file does not exist
  if(blocknumber == -1){
	printf("File does not exist\n");
	return;
  }
   
  //Store the file's index block to the class member data fileIndexBlock
  fileIndexBlock = blocknumber;


  //Read the file's index block from the disk
  char* fIndex = new char[blocksize];
  FileIndex* p = (FileIndex*)fIndex;
  disk->ReadBlock( blocknumber, fIndex );

  //Set the class member data openFile to reflect that there is now a file open
  openFile = true;

  //Read and print the data blocks of the file
  for (int i=0; i<p->fileSize; i++){
        char* tmp = new char[blocksize];
        disk->ReadBlock(p->blocks[i], tmp);
        int j = 0;
        while (tmp[j]!=0){
            printf("%c", tmp[j]);
            j++;
        }
        delete tmp;
  }
  printf("\n");

  //Initialize the class member data for later reads
  fileOffset = 0;
  editBlock = 0;

  //Clean up memory
  delete fIndex;

}


//seek function: seeks to a certain byte of a file
void FileSystem::seek( int offset ) {

  //Error case: no open file
  if(!openFile){
	printf("No file open\n");
	return;
  }

  //Error case: offset is out of file size bounds
  if(offset > size()){
     printf("The file is of size %i: specified offset is out of bounds\n", size());
     return;
  }

  //Compute the data block to seek to and the offset within that data block
  int tmpblock = 0;
  while(offset > blocksize){
	tmpblock++;
	offset = offset - blocksize;
  }

  //Set the class member data to store the data block and offset computed
  fileOffset = offset;
  editBlock = tmpblock;

}


//read function: Reads the specified number of bytes from the file into the provided buffer
void FileSystem::read( int numbytes, char* buffer ) {

  //Error case: no open file
  if(!openFile){
	printf("No file open.\n");
 	return;
  }
  //Error case: requested to read a negative number of bytes
  if(numbytes < 0){
     printf("Invalid Argument, number of bytes specified must be >= 0.\n");
     return;
  }

  //If the current offset + the number of bytes requested to be read exceeds the length of the file
  // - set the number of bytes to be read to the number of bytes left in the file
  // - null terminate the string at the appropriate point
  int seekoffset = (editBlock * blocksize) + fileOffset;
  if(seekoffset + numbytes > size()){
	numbytes = size() - seekoffset;
	buffer[numbytes] = 0;
  }

  //Read the file's index block from the disk
  char* fIndex = new char[blocksize];
  FileIndex* p = (FileIndex*)fIndex;
  disk->ReadBlock( fileIndexBlock, fIndex );
  
  //Initialize temporary counters and indicies
  int tmpOffset = fileOffset;
  int bufferindex = 0;
  int tmpEditBlock = editBlock;
  int tmpoff = -1;
  
  //Reads the file's data into the buffer if the number of bytes requires multiple data blocks to be read
  //Reads until last data block is entered
  while ((numbytes + tmpOffset) > blocksize){
	char* tmp = new char[blocksize];
  	disk->ReadBlock(p->blocks[tmpEditBlock], tmp);
     	for(int i = tmpOffset; i < blocksize; i++){
		if(tmp[i] == 0){}
		else{
     			buffer[bufferindex] = tmp[i];
     			bufferindex++;
		}
     	}
     	numbytes = numbytes - (blocksize - tmpOffset);
        tmpOffset = 0;
        tmpEditBlock++;
     	delete tmp;
  }
  
  //Read data from the last (or only) data block to be read from
  char* tmp = new char[blocksize];
  disk->ReadBlock(p->blocks[tmpEditBlock], tmp);
  for(int i = tmpOffset; i < (tmpOffset + numbytes); i++){
     	buffer[bufferindex] = tmp[i];
     	bufferindex++;
	tmpoff = i;
  }
  
  //Set file offset to end of read
  editBlock = tmpEditBlock;
  fileOffset = tmpoff + 1;

  //Clean up memory
  delete tmp;
  delete fIndex;

}


//write function: writes content to a given file
void FileSystem::write( char* content ) {

  //Error case: no file open
  if(!openFile){
	printf("No file open.\n");
 	return;
  }

  //Read the index block of the file
  char* fIndex = new char[blocksize];
  FileIndex* p = (FileIndex*)fIndex;
  disk->ReadBlock( fileIndexBlock, fIndex );

  //Initialize temporary counters and indices
  int tmpOffset = fileOffset;
  int bufferindex = 0;
  int tmpEditBlock = editBlock;

  //Store whether the file exceeds it's current size
  bool longer = false;

  //Determine and store the length of the data to be written
  int bufflen = strlen(content);

  //Read the block currently being edited into memory
  char* tmpblock = new char[blocksize];
  disk->ReadBlock(p->blocks[tmpEditBlock], tmpblock);

  //Write the provided data to the file
  while (bufferindex < bufflen){

  	//While the offset is less than total block size, continue writing bytes to this block
    	if (tmpOffset < blocksize){
      		tmpblock[tmpOffset] = content[bufferindex];
      		tmpOffset++;
      		bufferindex++;
    	}

	//If the next byte of data needs to be written to the next block
        //  - Determine if the file has another data block already allocated to write to
        //  - If not, allocate a new block and update the file's index block to reflect this
	//  - Reset the offset and move to the next data block
	else{
		disk->WriteBlock(p->blocks[tmpEditBlock], tmpblock);
		if(tmpEditBlock + 1 > maxfileblocks) break;
		delete tmpblock;
		tmpblock = new char[blocksize];
      		if(tmpEditBlock + 1 == p->fileSize){
        		//allocate new block for file
        		int x = disk->AllocateBlock();
        		p->blocks[tmpEditBlock + 1] = x;
        		p->fileSize = p->fileSize + 1;
			longer = true;
			tmpEditBlock++;
      			tmpOffset = 0;
      		}
		else{
			tmpEditBlock++;
      			tmpOffset = 0;
      			disk->ReadBlock(p->blocks[tmpEditBlock], tmpblock);
		}
  	}
	
  }

  //Null terminate the string if the file's new size exceeds it's original size
  if(longer){
	tmpblock[tmpOffset] = 0;
  }

  //Write the last edited data block to the disk
  disk->WriteBlock(p->blocks[tmpEditBlock], tmpblock);

  //Write the file's updated index block
  disk->WriteBlock(fileIndexBlock, fIndex);

  //Set file offset to end of read
  editBlock = tmpEditBlock;
  fileOffset = tmpOffset;

  //Clean up memory
  delete tmpblock;
  delete fIndex;

}


//Size function: returns the size of a file
int FileSystem::size() {

  //If there is a file open return it's size
  if(openFile){

    //Read the file's index block to memory
    char* fIndex = new char[blocksize];
    FileIndex* p = (FileIndex*)fIndex;
    disk->ReadBlock( fileIndexBlock, fIndex );

    //Read the file's last data block into memory
    char* tmp = new char[blocksize];
    disk->ReadBlock(p->blocks[p->fileSize - 1], tmp);

    //Count the number of bytes in the last data block
    int j = 0;
    while (tmp[j]!=0){
	j++;
    }

    //Clean up memory
    delete tmp;
    delete fIndex;

    //Return the computed size ( (Total # of data blocks - 1) + amount of data in last data block )
    return (((p->fileSize - 1) * blocksize) + j);
  }


  //If there is *not* a file open, return -1 to signal this case
  else{
     	return -1;
  }

}


//close function: closes a file
void FileSystem::close() {

  //Error case: no file open
  if(!openFile){
	printf("No file to close\n");
	return;
  }

  //Set the class member data to reflect that there is no file ope
  openFile = false;
  fileOffset = -1 ;
  editBlock = -1;
  fileIndexBlock = -1;

}


