// Bastard File System
// (C) 2007 Rene Schickbauer
//
// The Bastard File System (BFS) is a small virtual FS for Applications
// that would normally use Tarballs and/or Zipfiles but have to dynamically
// change its contents
//
// WARNING: This is still ALPHA QUALITY CODE! Use with care!
//
//
#include "bfs_dir.h"
#include "bfs_data.h"
#include "bfs_file.h"
#include "bfs.h"
#include "bfs_convert.h"

#include <string.h>
#include <stdlib.h>
#include <stdio.h>

bool parseDir(sBFS* handle, char *path, DIRTREE* tree) {
     memset(tree, 0, sizeof(DIRTREE));
     
     // Need to copy string -> may be const!
     char locpath[10000];
     sprintf(locpath, "%s", path);
     
     char* cur = locpath;
     char *found;
     
     if(*cur != '/') {
		sprintf(handle->lastError, "Path '%s' must start with '/'", locpath);
		return false;
	}
	
	cur++;
	int elemcnt = 0;
	
	// Special case: root dir -> no path elements, nothing to parse
	if(*cur == '\0') {
		tree->elemcount = elemcnt;
		return true;
	}
	
	bool last = false;
	while(!last) {
		found = strstr(cur, "/");
		if(found) {
			if(*found == '\0') {
				last = true;
			} else {
				*found = '\0';
			}
			sprintf(tree->element[elemcnt].name, "%s", cur);
			tree->element[elemcnt].type[0] = BLOCKTYPE_FREE;
			cur = found;
			cur++;
			elemcnt++;
		} else {
			last = true;
			sprintf(tree->element[elemcnt].name, "%s", cur);
			tree->element[elemcnt].type[0] = BLOCKTYPE_FREE;
			elemcnt++;
		}

	}
	tree->elemcount = elemcnt;
	return true;
}

bool traverseDir(sBFS* handle, DIRTREE* tree) {
	int i, j;
	if(!tree->elemcount) {
		sprintf(handle->lastError, "No elements to traverse");
		return false;
	}	
	
	// Traverse root dir first
	DIRLIST list;
	if(!listDir_fromBlock(handle, 0, &list)) {
		return false;
	}
	for(i = 0; i < list.elemcount; i++) {
		if(strcmp(list.element[i].name, tree->element[0].name) == 0) {
			tree->element[0].type[0] = list.element[i].type[0];
			tree->element[0].startblock = list.element[i].startblock;
		}
	}
	
	if(tree->element[0].type[0] == BLOCKTYPE_FREE) {
		sprintf(handle->lastError, "File not found");
		return false;		
	} else if(tree->element[0].type[0] == BLOCKTYPE_FILE) {
		return true; // Ok, we found the file
	} else if(tree->element[0].type[0] == BLOCKTYPE_DIR && tree->elemcount == 1) {
		return true; // Got the full path
	}

	for(i = 1; i < tree->elemcount; i++) {
		if(!listDir_fromBlock(handle, tree->element[i-1].startblock, &list)) {
			return false;
		}
		for(j = 0; j < list.elemcount; j++) {
			if(strcmp(list.element[j].name, tree->element[i].name) == 0) {
				tree->element[i].type[0] = list.element[j].type[0];
				tree->element[i].startblock = list.element[j].startblock;
			}
		}
		if(tree->element[i].type[0] == BLOCKTYPE_FREE) {
			sprintf(handle->lastError, "File not found");
			return false;		
		} else if(tree->element[i].type[0] == BLOCKTYPE_FILE) {
			return true; // Ok, we found the file
		} else if(tree->element[i].type[0] == BLOCKTYPE_DIR && i == (tree->elemcount - 1)) {
			return true; // Got the full path
		}
	}
	return false;
}

bool addDirNode(sBFS* handle, char type, char* path, int* createdBlock) {
	int startblock;
	int i;

	DIRTREE tree;
	if(!parseDir(handle, path, &tree)) {
		return false;
	}
	
	bool ret = traverseDir(handle, &tree);
	if(ret) {
		// Ups, already exists
		sprintf(handle->lastError, "File '%s' already exists", path);
		return false;
	} else if(tree.elemcount > 1) {
		// Check, if PATH exists
		if(tree.element[tree.elemcount-2].type[0] != BLOCKTYPE_DIR) {
			sprintf(handle->lastError, "Parent directory of '%s' does not exist", path);
			return false;
		} else {
			startblock = tree.element[tree.elemcount-2].startblock;
		}
	} else {
		startblock = 0; // Root dir
	}
	
	// Search a free data block
	int datablock = 0;
	for(i = 0; i < MAXDATABLOCKS; i++) {
		if(handle->blocktype[i] == BLOCKTYPE_FREE) {
			handle->blocktype[i] = type;
			datablock = i;
			break;
		}
	}
	if(!datablock) {
		sprintf(handle->lastError, "FS Full: No more free blocks for file %s", path);
		return false;
	}
	
	if(type == BLOCKTYPE_FILE) {
		// FIXME
	} else {
		BLOCK_DIR dirblock;
	    memset(&dirblock, 0, sizeof(dirblock));
	    //   Prepare root dir block
	    convertTo4Char(0, dirblock.prevBlock); // is First block
	    convertTo4Char(0, dirblock.nextBlock); // no more blocks
	    for(i = 0; i < DIRENTRY_PER_BLOCK; i++) { // Reset all entries
	        dirblock.entry[i].type[0] = BLOCKTYPE_FREE;
	    }
	    if(!writeDataBlock_Dir(handle, datablock, &dirblock)) {
			handle->blocktype[datablock] = BLOCKTYPE_FREE;
	        return false;
	    }
	}
	
	// Search for free dirblock, if none found -> add one
     bool readNext = true;
     int nextBlock = startblock;
     bool found = false;
     BLOCK_DIR block;
     while(readNext && !found) {
		if(!readDataBlock_Dir(handle, nextBlock, &block)) {
			handle->blocktype[datablock] = BLOCKTYPE_FREE;
			return false;
		}
		for(i = 0; i < DIRENTRY_PER_BLOCK; i++) {
			if(!found && block.entry[i].type[0] == BLOCKTYPE_FREE) {
				// Jippie, got a free slot;
				found = true;
				block.entry[i].type[0] = type;
				convertTo4Char(datablock, block.entry[i].startblock);
				sprintf(block.entry[i].name, "%s", tree.element[tree.elemcount-1].name);
				if(!writeDataBlock_Dir(handle, nextBlock, &block)) {
					handle->blocktype[datablock] = BLOCKTYPE_FREE;
					return false;
				}
			}
		}
		if(found) {
			break;
		}

		if(convertFrom4Char(block.nextBlock)) {
			nextBlock = convertFrom4Char(block.nextBlock);
		} else {
			break;
		}
	}
	
	if(!found) {
		// Ok, we didn't find a free slot -> add a new block to dirlist
		int newdirblock = 0;
		for(i = 0; i < MAXDATABLOCKS; i++) {
			if(handle->blocktype[i] == BLOCKTYPE_FREE) {
				handle->blocktype[i] = BLOCKTYPE_DIR;
				newdirblock = i;
				break;
			}
		}
		if(!newdirblock) {
			sprintf(handle->lastError, "FS Full: No more free blocks for dir %s", path);
			handle->blocktype[datablock] = BLOCKTYPE_FREE;
			return false;
		}
		
		// Adapt last block of existing chain
		convertTo4Char(newdirblock, block.nextBlock);
		if(!writeDataBlock_Dir(handle, nextBlock, &block)) {
			handle->blocktype[datablock] = BLOCKTYPE_FREE;
			handle->blocktype[newdirblock] = BLOCKTYPE_FREE;
			return false;
		}

		// prepare new block		
	    memset(&block, 0, sizeof(block));
	    //   Prepare root dir block
	    convertTo4Char(0, block.prevBlock); // is First block
	    convertTo4Char(0, block.nextBlock); // no more blocks
	    for(i = 0; i < DIRENTRY_PER_BLOCK; i++) { // Reset all entries
	        block.entry[i].type[0] = BLOCKTYPE_FREE;
	    }
		block.entry[0].type[0] = type;
		convertTo4Char(datablock, block.entry[0].startblock);
		sprintf(block.entry[0].name, "%s", tree.element[tree.elemcount-1].name);

	    
	    if(!writeDataBlock_Dir(handle, newdirblock, &block)) {
			handle->blocktype[datablock] = BLOCKTYPE_FREE;
			handle->blocktype[newdirblock] = BLOCKTYPE_FREE;
			// If we fail here, we probably fucked up our FS, add info to errormsg
			strcat(handle->lastError, " !!UPS - failed at critical moment, data corruption in progress!!");
	        return false;
	    }
	}
		
	*createdBlock = datablock;
	return true;

}

bool rmDirNode(sBFS* handle, char* path) {
	handle = handle; // BLA
	path = path; // BLA
     printf("NOT IMPLEMENTED\n");
     return false;
}

bool dumpDirListTree(sBFS* handle, char* path, char* spacer) {
	char mspacer[1000];
	sprintf(mspacer, "%s  ", spacer);
	DIRLIST list;
	char subpath[1000];
	int i;
	int blocks, bytes;
	
	if(!listDir(handle, path, &list)) {
        printf("ERROR in %s: %s\n", path, getLastError(handle));
        return false;
    } else {
		for(i = 0; i < list.elemcount; i++) {
			if(list.element[i].type[0] == BLOCKTYPE_DIR) {
				printf("%c%s%s\n", list.element[i].type[0], spacer, list.element[i].name);
			} else if(list.element[i].type[0] == BLOCKTYPE_FILE) {
                if(!statFile_fromBlock(handle, list.element[i].startblock, &blocks, &bytes)) {
                    return false;
                }
                printf("%c%s%s\t%d bytes\t%d blocks\n", list.element[i].type[0], spacer, list.element[i].name, bytes, blocks);
                
            }
			
			if(list.element[i].type[0] == BLOCKTYPE_DIR) {
				// Recurse
				sprintf(subpath, "%s", path);
				if(strcmp(subpath, "/") != 0) {
					strcat(subpath, "/");
				}
				strcat(subpath, list.element[i].name);
				if(!dumpDirListTree(handle, subpath, mspacer)) {
					return false;
				}
			}
		}
	}
	return true;
}
			

bool listDir(sBFS* handle, char* path, DIRLIST* list) {
     if(strcmp(path, "/") == 0) {
		// Handle Root-Dir
		return listDir_fromBlock(handle, 0, list);
	} else {
		// Handle subdir
		DIRTREE tree;
		if(!parseDir(handle, path, &tree)) {
			return false;
		}
			
		if(!traverseDir(handle, &tree)) {
				return false;
		}
		return listDir_fromBlock(handle, tree.element[tree.elemcount-1].startblock, list);
	}
	return false;
}

bool listDir_fromBlock(sBFS* handle, int startblock, DIRLIST* list) {
     memset(list, 0, sizeof(DIRLIST));
     
     bool readNext = true;
     int nextBlock = startblock;
     int elemcnt = 0;
     BLOCK_DIR block;
     int i;
     while(readNext) {
		if(!readDataBlock_Dir(handle, nextBlock, &block)) {
			return false;
		}
		for(i = 0; i < DIRENTRY_PER_BLOCK; i++) {
			list->element[elemcnt].type[0] = block.entry[i].type[0];
			sprintf(list->element[elemcnt].name, "%s", block.entry[i].name);
			list->element[elemcnt].startblock = convertFrom4Char(block.entry[i].startblock);
			// TODO: For files: Lookup file size
			elemcnt++;
		}
		nextBlock = convertFrom4Char(block.nextBlock);
		if(!nextBlock) {
			readNext = false;
		}
	}
	list->elemcount = elemcnt;
     
    return true;
}

