#include <stdio.h>
#include <stdlib.h>   /* exit()    */
#include <string.h>   /* strncpy() */
#include <sys/stat.h> /* struct stat */
#include <fcntl.h>    /* O_RDONLY */
#include <unistd.h>   /* lseek() */
#include <ctype.h>    /* toupper() */
#include <errno.h>
#include <time.h>

#include "version.h"

#include "prodos.h"

static int ignore_errors=0;
int debug=0;



	/* Given filename, return voldir/offset */
static int prodos_lookup_file(struct voldir_t *voldir,
					int subdir_block,
					char *filename) {

	int voldir_block,voldir_offset;
	struct file_entry_t file_entry;
	unsigned char voldir_buffer[PRODOS_BYTES_PER_BLOCK];
	int result,file;

	if (debug) printf("\t*** Looking for %s in block $%X\n",filename,subdir_block);

	voldir_block=subdir_block;
	voldir_offset=1;       /* skip the header */

	while(1) {

		/* Read in Block */
		result=prodos_read_block(voldir,
				voldir_buffer,voldir_block);
		if (result<0) {
			fprintf(stderr,"Error on I/O\n");
			return -1;
		}

		for(file=voldir_offset;
			file<voldir->entries_per_block;file++) {

			prodos_populate_filedesc(
				voldir_buffer+4+(file*PRODOS_FILE_DESC_LEN),
				&file_entry);

			if (file_entry.storage_type==PRODOS_FILE_DELETED) continue;


			if (debug) printf("\tTrying $%X = %s\n",
				(voldir_block<<8)|file,
				file_entry.file_name);

			/* FIXME: case insensitive? */
			if (!strncmp(filename,(char *)file_entry.file_name,15)) {
				if (debug) printf("*** MATCH: %s = %s at inode $%X\n",
						filename,file_entry.file_name,
						(voldir_block<<8)|file);
				return (voldir_block<<8)|file;
			}
		}

		voldir_offset=0;
		voldir_block=voldir_buffer[2]|(voldir_buffer[3]<<8);
		if (voldir_block==0) break;

	}

	return -1;
}






	/* Given dir_block fine an empty entry */
	/* FIXME: allocate new voldir block if all full */
static int prodos_allocate_directory_entry(
		struct voldir_t *voldir,int dir_block) {

	int voldir_block,voldir_offset;
	struct file_entry_t file_entry;
	unsigned char voldir_buffer[PRODOS_BYTES_PER_BLOCK];
	int result,file;

	if (debug) printf("*** Looking for empty file entry in $%X\n",dir_block);

	voldir_block=dir_block;
	voldir_offset=1;       /* skip the header */

	while(1) {

		/* Read in Block */
		result=prodos_read_block(voldir,
				voldir_buffer,voldir_block);
		if (result<0) {
			fprintf(stderr,"Error on I/O\n");
			return -1;
		}

		for(file=voldir_offset;
			file<voldir->entries_per_block;file++) {

			prodos_populate_filedesc(
				voldir_buffer+4+file*PRODOS_FILE_DESC_LEN,
				&file_entry);

			if (debug) printf("\tTrying $%X status %X\n",
					(voldir_block<<8)|file,
					file_entry.storage_type);
			if (file_entry.storage_type==PRODOS_FILE_DELETED) {
				return (voldir_block<<8)|file;
			}
		}

		voldir_offset=0;
		voldir_block=voldir_buffer[2]|(voldir_buffer[3]<<8);

		if (voldir_block==0) break;

	}

	return -1;
}



	/* Checks if "filename" exists */
	/* returns file type  */
static int prodos_check_file_exists(struct voldir_t *voldir,
					int dir_block,
					char *filename) {


	int result;

	result=prodos_lookup_file(voldir,dir_block,filename);

	return result;
}

static int prodos_free_block(struct voldir_t *voldir,int block) {

	int result;

	/* mark as free using VOLDIR */
	result=prodos_voldir_free_block(voldir,block);

	if (result<0) {
		fprintf(stderr,"Error on I/O\n");
	}

	return 0;
}

static int prodos_allocate_block(struct voldir_t *voldir) {

	int found_block=0;

	/* Find an empty block */
	found_block=prodos_voldir_find_free_block(voldir);
	if (debug) printf("Found free block %x\n",found_block);

	if (found_block<0) {
		fprintf(stderr,"ERROR: prodos_allocate_sector: Disk full!\n");
		return -1;
	}

	prodos_voldir_reserve_block(voldir,found_block);

	return found_block;

}


static int prodos_writeout_filedesc(struct voldir_t *voldir,
				struct file_entry_t *file_entry,
				unsigned char *dest) {

	/* clear it out */
	memset(dest,0,PRODOS_FILE_DESC_LEN);

	dest[0x00]=(file_entry->storage_type<<4)|(file_entry->name_length&0xf);
        memcpy(&dest[0x01],&file_entry->file_name[0],file_entry->name_length);

	dest[0x10]=file_entry->file_type;

	dest[0x11]=file_entry->key_pointer&0xff;
	dest[0x12]=(file_entry->key_pointer>>8)&0xff;

	dest[0x13]=file_entry->blocks_used&0xff;
	dest[0x14]=(file_entry->blocks_used>>8)&0xff;

	dest[0x15]=file_entry->eof&0xff;
	dest[0x16]=(file_entry->eof>>8)&0xff;
	dest[0x17]=(file_entry->eof>>16)&0xff;

	dest[0x18]=(file_entry->creation_time>>16)&0xff;
	dest[0x19]=(file_entry->creation_time>>24)&0xff;
	dest[0x1a]=(file_entry->creation_time>>0)&0xff;
	dest[0x1b]=(file_entry->creation_time>>8)&0xff;

	dest[0x1c]=file_entry->version;
	dest[0x1d]=file_entry->min_version;
	dest[0x1e]=file_entry->access;

	dest[0x1f]=file_entry->aux_type&0xff;
	dest[0x20]=(file_entry->aux_type>>8)&0xff;

	dest[0x21]=(file_entry->last_mod>>16)&0xff;
	dest[0x22]=(file_entry->last_mod>>24)&0xff;
	dest[0x23]=(file_entry->last_mod>>0)&0xff;
	dest[0x24]=(file_entry->last_mod>>8)&0xff;

	dest[0x25]=file_entry->header_pointer&0xff;
	dest[0x26]=(file_entry->header_pointer>>8)&0xff;

	return 0;
}


#define ERROR_MYSTERY		1
#define ERROR_INVALID_FILENAME	2
#define ERROR_FILE_NOT_FOUND	3
#define ERROR_NO_SPACE		4
#define ERROR_IMAGE_NOT_FOUND	5
#define ERROR_CATALOG_FULL	6
#define ERROR_FILE_TOO_BIG	7



#define PRODOS_NUM_FILE_TYPES 5

static struct prodos_file_type {
	int type;
	char name[4];
} file_types[PRODOS_NUM_FILE_TYPES] = {
	{PRODOS_TYPE_TXT,"TXT"},
	{PRODOS_TYPE_BIN,"BIN"},
	{PRODOS_TYPE_BAS,"BAS"},
	{PRODOS_TYPE_VAR,"VAR"},
	{PRODOS_TYPE_SYS,"SYS"},
};


	/* creates file apple_filename on the image from local file filename */
	/* returns ?? */
static int prodos_add_file(struct voldir_t *voldir,
		int dir_block, char *type,
		int address, int length,
		char *filename, char *apple_filename) {


	int free_blocks,file_size,needed_blocks,total_blocks,storage_type;
	int block,i,j,needed_limit;
	struct stat file_info;
	int input_fd;
	int result;
	unsigned char key_buffer[PRODOS_BYTES_PER_BLOCK];
	unsigned char index_buffer[PRODOS_BYTES_PER_BLOCK];
	unsigned char data_buffer[PRODOS_BYTES_PER_BLOCK];
	int key_block,index,inode;
	struct file_entry_t file;
	int file_type=0;


	/* check for valid filename */

	/* Filename rules for Prodos: */
	/*	Only letters, numbers, and periods */
	/*	no special chars */
	/* Traditionally only uppercase (for II+).  Upper/lowercase */
	/*	data later stored in the VERSION/MAX_VERSION fields */

	for(i=0;i<strlen(apple_filename);i++) {
		if ( (!isalnum(apple_filename[i])) &&
			(apple_filename[i]!='.') ) {
			fprintf(stderr,"Warning!  Invalid char in filename!\n");
			//return ERROR_INVALID_FILENAME;
		}
		if ( (isalpha(apple_filename[i])) &&
				(islower(apple_filename[i])) ) {
			fprintf(stderr,"Warning, lowercase filename support not really implemented\n");
			//return ERROR_INVALID_FILENAME;
		}
	}

	/* get file type */
	for(i=0;i<PRODOS_NUM_FILE_TYPES;i++) {
		if (!strncmp(type,file_types[i].name,3)) {
			file_type=file_types[i].type;
			if (debug) printf("Found type %s=%d\n",type,file_type);
		}
	}

	/* Determine size of file to upload */
	if (stat(filename,&file_info)<0) {
		fprintf(stderr,"Error!  %s not found!\n",filename);
		return ERROR_FILE_NOT_FOUND;
	}

	file_size=(int)file_info.st_size;

	/* We need to round up to nearest block size */
	needed_blocks=1+((file_size-1)/PRODOS_BYTES_PER_BLOCK);

	if (debug) printf("Filesize: %d (need %d blocks)\n",
			file_size,needed_blocks);


	if (needed_blocks==0) {
		fprintf(stderr,"Error!  invalid blocksize %d\n",needed_blocks);
		return -ERROR_MYSTERY;
	}
	else if (needed_blocks==1) {
		/* seedling */
		if (debug) printf("File seedling\n");
		storage_type=PRODOS_FILE_SEEDLING;
		total_blocks=needed_blocks;
	}
	else if (needed_blocks<=256) {
		/* sapling */
		if (debug) printf("File sapling\n");
		storage_type=PRODOS_FILE_SAPLING;
		total_blocks=needed_blocks+1;	/* for index block */
	}
	else if (needed_blocks<=65536) {
		/* tree */
		if (debug) printf("File tree\n");
		storage_type=PRODOS_FILE_TREE;
		total_blocks=needed_blocks+1 /* for key index block */
			+(1+needed_blocks/256);	/* for index blocks */
						/* FIXME: -1? */
	}
	else {
		fprintf(stderr,"Error, file too big: %d\n",file_size);
		return -ERROR_FILE_TOO_BIG;
	}

	/* Get free space on device */
	free_blocks=prodos_voldir_free_space(voldir);

	/* Check for free space */
	if (total_blocks>free_blocks) {
		fprintf(stderr,"Error!  Not enough free space "
				"on disk image (need %d have %d)\n",
				total_blocks,free_blocks);
		return ERROR_NO_SPACE;
	}

	/* Open the local file */
	input_fd=open(filename,O_RDONLY);
	if (input_fd<0) {
		fprintf(stderr,"Error! could not open %s\n",filename);
		return ERROR_IMAGE_NOT_FOUND;
	}

	if (storage_type==PRODOS_FILE_SEEDLING) {
		block=prodos_allocate_block(voldir);
		key_block=block;

		memset(data_buffer,0,PRODOS_BYTES_PER_BLOCK);
		result=read(input_fd,data_buffer,PRODOS_BYTES_PER_BLOCK);
		if (result<0) {
			fprintf(stderr,"Error reading\n");
			return -ERROR_MYSTERY;
		}
		prodos_write_block(voldir,data_buffer,block);
	}

	if (storage_type==PRODOS_FILE_SAPLING) {
		/* allocate index */
		index=prodos_allocate_block(voldir);
		key_block=index;
		if (debug) printf("**** ALLOCATING SAPLING key_block=$%X\n",key_block);

		memset(index_buffer,0,PRODOS_BYTES_PER_BLOCK);

		for(i=0;i<needed_blocks;i++) {
			block=prodos_allocate_block(voldir);

			index_buffer[i]=block&0xff;
			index_buffer[i+256]=(block>>8)&0xff;

			memset(data_buffer,0,PRODOS_BYTES_PER_BLOCK);

			result=read(input_fd,data_buffer,PRODOS_BYTES_PER_BLOCK);
			if (result<0) {
				fprintf(stderr,"Error reading\n");
				return -ERROR_MYSTERY;
			}
			prodos_write_block(voldir,data_buffer,block);
		}
		prodos_write_block(voldir,index_buffer,index);
	}


	if (storage_type==PRODOS_FILE_TREE) {
		/* allocate key index */
		key_block=prodos_allocate_block(voldir);
		memset(key_buffer,0,PRODOS_BYTES_PER_BLOCK);

		for(j=0;j<(1+needed_blocks/256);j++) {
			index=prodos_allocate_block(voldir);
			memset(index_buffer,0,PRODOS_BYTES_PER_BLOCK);

			key_buffer[j]=index&0xff;
			key_buffer[j+256]=(index>>8)&0xff;

			if (j==needed_blocks/256) {
				needed_limit=needed_blocks%256;
			}
			else {
				needed_limit=256;
			}

			for(i=0;i<needed_limit;i++) {
				block=prodos_allocate_block(voldir);

				index_buffer[i]=block&0xff;
				index_buffer[i+256]=(block>>8)&0xff;

				memset(data_buffer,0,PRODOS_BYTES_PER_BLOCK);

				result=read(input_fd,data_buffer,PRODOS_BYTES_PER_BLOCK);
				if (result<0) {
					fprintf(stderr,"Error reading\n");
					return -ERROR_MYSTERY;
				}
				prodos_write_block(voldir,data_buffer,block);
			}
			prodos_write_block(voldir,index_buffer,index);
		}
		prodos_write_block(voldir,key_buffer,key_block);
	}

	close(input_fd);

	/* now that file is on disk, hook up the directory image */

	memset(&file,0,sizeof(struct file_entry_t));


	/* FIXME */
	file.storage_type=storage_type;
	file.name_length=strlen(apple_filename);
	memcpy(file.file_name,apple_filename,file.name_length);
	file.file_type=file_type;
	file.key_pointer=key_block;
	file.blocks_used=total_blocks;	/* includes index blocks */
	file.eof=file_size;
	file.creation_time=prodos_time(time(NULL));
	file.version=0;
	file.min_version=0;
	file.access=0xe3;	// 0x21?
	if (file.file_type==PRODOS_TYPE_BIN) {
		file.aux_type=address;
	}
	else {
		file.aux_type=0;
	}
	file.last_mod=prodos_time(time(NULL));
	file.header_pointer=dir_block;


	inode=prodos_allocate_directory_entry(voldir,dir_block);
	if (inode<0) {
		return inode;
	}

	if (debug) printf("Found inode $%x\n",inode);

	/* read in existing voldir entry */
	result=prodos_read_block(voldir,data_buffer,inode>>8);

	/* copy in new data */
	prodos_writeout_filedesc(voldir,&file,
		data_buffer+4+(inode&0xff)*PRODOS_FILE_DESC_LEN);

	/* write back existing voldir entry */
	result=prodos_write_block(voldir,data_buffer,inode>>8);

	/* update file count */
	if (debug) printf("Updating file count...\n");
	voldir->file_count++;
	prodos_sync_voldir(voldir);

	return 0;
}


int prodos_get_file_entry(struct voldir_t *voldir,
			int inode, struct file_entry_t *file) {

	unsigned char voldir_buffer[PRODOS_BYTES_PER_BLOCK];
	int block,offset;
	int result;

	block=(inode>>8);
	offset=(inode&0xff);


	result=prodos_read_block(voldir,voldir_buffer,block);
	if (result<0) {
		return result;
	}

	prodos_populate_filedesc(voldir_buffer+4+offset*PRODOS_FILE_DESC_LEN,
			file);

	return 0;


}



static int prodos_get_directory(struct voldir_t *voldir,char *string) {

	char path[4096];
	int pointer=0,path_pointer=0;
	int inode,final_pointer=-1;
	struct file_entry_t file;
	int subdir_block=PRODOS_VOLDIR_KEY_BLOCK;

	if (debug) printf("Looking up directory %s\n",string);


	while(1) {
		path_pointer=0;

		/* see if leading '/' */
		/* also handles the plain "/" case */
		/* FIXME: eat multiple //// */
		if (string[pointer]=='/') {
			pointer++;
			/* special case "/" */
			if (string[pointer]==0) return subdir_block;
		}

		while(string[pointer]!=0) {
			if (string[pointer]=='/') break;
			path[path_pointer]=string[pointer];
			pointer++;
			path_pointer++;
		}
		path[path_pointer]=0;	/* NUL terminate */

		if (debug) printf("Found subdir %s\n",path);

		/* get the voldir/entry for file */
		inode=prodos_lookup_file(voldir,subdir_block,path);
		if (inode<0) {
			fprintf(stderr,"Error!  %s not found!\n",
					path);
			return -1;
		}

		/* get the file entry */
		if (prodos_get_file_entry(voldir,inode,&file)<0) {
			fprintf(stderr,"Error opening inode %x\n",inode);
			return -1;
		}

		if (file.storage_type!=PRODOS_FILE_SUBDIR) {
			fprintf(stderr,"Error!  %s is not a subdir!\n",path);
			return -1;
		}
		else {
			final_pointer=file.key_pointer;
		}

		subdir_block=final_pointer;

		if (string[pointer]==0) break;
	}

	return final_pointer;

}


	/* load a file from the disk image.  */
	/* inode = voldirblock<<8 | entry */
static int prodos_load_file(struct voldir_t *voldir,
		int inode,char *filename) {

	int output_fd;
	unsigned char data[PRODOS_BYTES_PER_BLOCK];
	unsigned char index_block[PRODOS_BYTES_PER_BLOCK];
	unsigned char master_index_block[PRODOS_BYTES_PER_BLOCK];
	int result,chunk,chunk_block,index,blocks_left,read_blocks;
	struct file_entry_t file;

	/* FIXME!  Warn if overwriting file! */
	output_fd=open(filename,O_WRONLY|O_CREAT|O_TRUNC,0666);
	if (output_fd<0) {
		fprintf(stderr,"Error! could not open %s for local save\n",
			filename);
		return -1;
	}

	if (prodos_get_file_entry(voldir,inode,&file)<0) {
		fprintf(stderr,"Error opening inode %x\n",inode);
		return -1;
	}


	switch(file.storage_type) {
		case PRODOS_FILE_SEEDLING:
			/* Just a single block */
			if (debug) fprintf(stderr,"Loading %d bytes from "
					"block $%x\n",
					file.eof,file.key_pointer);
			result=prodos_read_block(voldir,data,
					file.key_pointer);
			if (result<0) {
				return result;
			}
			result=write(output_fd,data,file.eof);
			if (result!=file.eof) {
				fprintf(stderr,"Error writing file!\n");
				return -1;
			}
			break;

		case PRODOS_FILE_SAPLING:
			/* Index block points to up to 256 blocks */
			/* Addresses are stored low-byte (256 bytes) then hi-byte */
			/* Address of zero means file hole, all zeros */
			if (debug) fprintf(stderr,"Loading index "
					"block $%x\n",
					file.key_pointer);
			result=prodos_read_block(voldir,index_block,
					file.key_pointer);
			if (result<0) {
				return result;
			}

			for(chunk=0;chunk<file.blocks_used;chunk++) {
				chunk_block=(index_block[chunk])|(index_block[chunk+256]<<8);

				if (chunk_block==0) {
					/* FILE hole */
					lseek(output_fd,
						PRODOS_BYTES_PER_BLOCK,
						SEEK_CUR);
				}
				else {
					result=prodos_read_block(voldir,data,
							chunk_block);
					if (result<0) {
						return result;
					}
				}

				result=write(output_fd,data,PRODOS_BYTES_PER_BLOCK);
				if (result!=PRODOS_BYTES_PER_BLOCK) {
					fprintf(stderr,"Error writing file!\n");
					return -1;
				}
			}
			/* truncate to actual size of file */
			ftruncate(output_fd,file.eof);

			break;

		case PRODOS_FILE_TREE:
			/* Master Index block points to up to 256 index blocks */
			/* Addresses are stored low-byte (256 bytes) then hi-byte */
			/* Index block points to up to 256 blocks */
			/* Addresses are stored low-byte (256 bytes) then hi-byte */
			/* Address of zero means file hole, all zeros */

			blocks_left=file.blocks_used;

			if (debug) fprintf(stderr,"Loading master index "
					"block $%x\n",
					file.key_pointer);
			result=prodos_read_block(voldir,master_index_block,
					file.key_pointer);
			if (result<0) {
				return result;
			}

			for(index=0;index<file.blocks_used/256;index++) {
				result=prodos_read_block(voldir,
					index_block,
					(master_index_block[index])|
					(master_index_block[256+index]<<8));
				if (result<0) {
					return result;
				}

				if (blocks_left<256) {
					read_blocks=blocks_left;
				}
				else {
					read_blocks=256;
				}

				for(chunk=0;chunk<read_blocks;chunk++) {
					chunk_block=(index_block[chunk])|
						(index_block[chunk+256]<<8);

					result=prodos_read_block(voldir,data,
							chunk_block);
					if (result<0) {
						return result;
					}


					result=write(output_fd,data,PRODOS_BYTES_PER_BLOCK);
					if (result!=PRODOS_BYTES_PER_BLOCK) {
						fprintf(stderr,"Error writing file!\n");
						return -1;
					}
				}
			}
			/* truncate to actual size of file */
			ftruncate(output_fd,file.eof);

			break;


		case PRODOS_FILE_SUBDIR:
		case PRODOS_FILE_DELETED:
		case PRODOS_FILE_SUBDIR_HDR:
		case PRODOS_FILE_VOLUME_HDR:
		default:
			fprintf(stderr,"Error!  "
				"Cannot load this type of file: %x\n",
				file.storage_type);
			break;
	}

	close(output_fd);

	return 0;

}



    /* rename a file */
    /* FIXME: validate the new filename is valid */
static int prodos_rename_file(struct voldir_t *voldir,
	char *old_filename,char *new_filename) {

	int result;
	int inode;
	struct file_entry_t file;
	unsigned char data_buffer[PRODOS_BYTES_PER_BLOCK];
	int newlen;

	/* get the voldir/entry for file */
	inode=prodos_lookup_file(voldir,PRODOS_VOLDIR_KEY_BLOCK,old_filename);

	if (inode<0) {
		fprintf(stderr,"Error!  %s not found!\n",
				old_filename);
		return -1;
	}

	/* get the file entry */
	if (prodos_get_file_entry(voldir,inode,&file)<0) {
		fprintf(stderr,"Error opening inode %x\n",inode);
		return -1;
	}

	/* change the filename */
	newlen=strlen(new_filename);
	memset(file.file_name,0,15);
	memcpy(file.file_name,new_filename,newlen);

	file.name_length=newlen;

	/* read in existing voldir entry */
	result=prodos_read_block(voldir,data_buffer,inode>>8);

	/* copy in new data */
	prodos_writeout_filedesc(voldir,&file,
		data_buffer+4+(inode&0xff)*PRODOS_FILE_DESC_LEN);

	/* write back existing voldir entry */
	result=prodos_write_block(voldir,data_buffer,inode>>8);
	if (result<0) {
		fprintf(stderr,"I/O Error!\n");
		return result;
	}

	return 0;
}


static int prodos_delete_file(struct voldir_t *voldir,
			int dir_block,char *apple_filename) {

	unsigned char data_buffer[PRODOS_BYTES_PER_BLOCK];
	unsigned char index_block[PRODOS_BYTES_PER_BLOCK];
	unsigned char master_index_block[PRODOS_BYTES_PER_BLOCK];
	int result,chunk,chunk_block,index,blocks_left,read_blocks,mblock;
	struct file_entry_t file;
	int inode;

	if (debug) printf("*** DELETING FILE %s\n",apple_filename);

	/* get the voldir/entry for file */
	inode=prodos_lookup_file(voldir,dir_block,apple_filename);
	if (debug) printf("\t*** Found inode $%X for file %s\n",inode,apple_filename);

	if (inode<0) {
		fprintf(stderr,"Error!  %s not found!\n",
				apple_filename);
		return -1;
	}

	result=prodos_read_block(voldir,data_buffer,inode>>8);

	if (prodos_get_file_entry(voldir,inode,&file)<0) {
		fprintf(stderr,"Error opening inode %x\n",inode);
		return -1;
	}

	if (debug) printf("*** PERMANENTLY DELETING %s (inode: $%X)\n",
		file.file_name,inode);

	/******************************/
	/* delete all the file blocks */
	/******************************/

	switch(file.storage_type) {
		case PRODOS_FILE_SEEDLING:
			/* Just a single block */
			if (debug) fprintf(stderr,"*** SEEDLING Deleting block $%x\n",
				file.key_pointer);

			result=prodos_free_block(voldir,file.key_pointer);
			if (result<0) {
				return result;
			}
			break;

		case PRODOS_FILE_SAPLING:
			/* Index block points to up to 256 blocks */
			/* Addresses are stored low-byte (256 bytes) then hi-byte */
			/* Address of zero means file hole, all zeros */
			if (debug) printf("*** SAPLING "
					"Freeing index block $%x\n",
					file.key_pointer);
			result=prodos_read_block(voldir,index_block,
					file.key_pointer);
			if (result<0) {
				return result;
			}

			for(chunk=0;chunk<file.blocks_used;chunk++) {
				chunk_block=(index_block[chunk])|(index_block[chunk+256]<<8);

				if (chunk_block==0) {
					/* FILE hole */
				}
				else {
					result=prodos_free_block(voldir,chunk_block);
					if (result<0) {
						return result;
					}
				}
			}
			result=prodos_free_block(voldir,file.key_pointer);
			break;

		case PRODOS_FILE_TREE:
			/* Master Index block points to up to 256 index blocks */
			/* Addresses are stored low-byte (256 bytes) then hi-byte */
			/* Index block points to up to 256 blocks */
			/* Addresses are stored low-byte (256 bytes) then hi-byte */
			/* Address of zero means file hole, all zeros */

			blocks_left=file.blocks_used;

			if (debug) fprintf(stderr,"*** TREE: "
					"Deleting master index block $%x\n",
					file.key_pointer);
			result=prodos_read_block(voldir,master_index_block,
					file.key_pointer);
			if (result<0) {
				return result;
			}

			for(index=0;index<file.blocks_used/256;index++) {
				mblock=(master_index_block[index])|
					(master_index_block[256+index]<<8);
				result=prodos_read_block(voldir,
					index_block,
					mblock);
				if (result<0) {
					return result;
				}

				if (blocks_left<256) {
					read_blocks=blocks_left;
				}
				else {
					read_blocks=256;
				}

				for(chunk=0;chunk<read_blocks;chunk++) {
					chunk_block=(index_block[chunk])|
						(index_block[chunk+256]<<8);

					result=prodos_free_block(voldir,chunk_block);
					if (result<0) {
						return result;
					}
				}
				result=prodos_free_block(voldir,mblock);
			}
			result=prodos_free_block(voldir,file.key_pointer);
			break;


		case PRODOS_FILE_SUBDIR:
		case PRODOS_FILE_DELETED:
		case PRODOS_FILE_SUBDIR_HDR:
		case PRODOS_FILE_VOLUME_HDR:
		default:
			fprintf(stderr,"Error!  "
				"Cannot delete this type of file: %x\n",
				file.storage_type);
			break;
	}


	/* now that file is gone, disconnect the directory image */

	/* should we clear it out? */
	/* makes undelete harder */
	/* ProDOS 1.0.1 clears filename but leaves rest */
	// memset(&file,0,sizeof(struct file_entry_t));

	file.storage_type=PRODOS_FILE_DELETED;
	file.name_length=0;
	memset(file.file_name,0,PRODOS_FILENAME_LEN);

	/* copy in new data */
	prodos_writeout_filedesc(voldir,&file,
		data_buffer+4+(inode&0xff)*PRODOS_FILE_DESC_LEN);

	/* write back existing voldir entry */
	result=prodos_write_block(voldir,data_buffer,inode>>8);

	/* update file count */
	if (debug) printf("Updating file count...\n");
	voldir->file_count--;
	prodos_sync_voldir(voldir);

	return 0;
}


static void display_help(char *name, int version_only) {
	printf("\nprodos version %s\n",VERSION);
	printf("by Vince Weaver <vince@deater.net>\n");
	printf("\n");

	if (version_only) return;

	printf("Usage: %s [-h] [i interleave] [-y] [-x] disk_image COMMAND [options]\n",name);
	printf("\t-h : this help message\n");
	printf("\t-i : interleave (prodos or dos33)\n");
	printf("\t-y : always answer yes for warning questions\n");
	printf("\t-x : ignore errors (useful for making invalid filenames)\n");
	printf("\n");
	printf("  Where disk_image is a valid PRODOS disk image\n"
		"  and COMMAND is one of the following:\n");
	printf("\tCATALOG   [dir_name]\n");
	printf("\tLOAD      apple_file <local_file>\n");
	printf("\tSAVE      [-t type] [-a addr] [-l len] local_file <apple_file>\n");
	printf("\tDELETE    apple_file\n");
	printf("\tRENAME    apple_file_old apple_file_new\n");
	printf("\tDUMP\n");
	printf("\tVOLUME    volume_name\n");
	printf("\tMKDIR     dir_name\n");
	printf("\tRMDIR     dir_name\n");
	printf("\tTYPE      TODO: set type\n");
	printf("\tAUX       TODO: set aux\n");
	printf("\tTIMESTAMP TODO: set timestamp\n");
	printf("\tACCESS    TODO: set access\n");

	printf("\n");
	return;
}

#define COMMAND_LOAD		0
#define COMMAND_SAVE		1
#define COMMAND_CATALOG		2
#define COMMAND_DELETE		3
#define COMMAND_RENAME		4
#define COMMAND_DUMP		5
#define COMMAND_SHOWFREE	6
#define COMMAND_VOLNAME		7
#define COMMAND_MKDIR		8
#define COMMAND_RMDIR		9

#define MAX_COMMAND		10
#define COMMAND_UNKNOWN		255

static struct command_type {
	int type;
	char name[32];
} commands[MAX_COMMAND] = {
	{COMMAND_LOAD,"LOAD"},
	{COMMAND_SAVE,"SAVE"},
	{COMMAND_CATALOG,"CATALOG"},
	{COMMAND_DELETE,"DELETE"},
	{COMMAND_RENAME,"RENAME"},
	{COMMAND_DUMP,"DUMP"},
	{COMMAND_SHOWFREE,"SHOWFREE"},
	{COMMAND_VOLNAME,"VOLNAME"},
	{COMMAND_MKDIR,"MKDIR"},
	{COMMAND_RMDIR,"RMDIR"},
};

static int lookup_command(char *name) {

	int which=COMMAND_UNKNOWN,i;

	for(i=0;i<MAX_COMMAND;i++) {
		if(!strncmp(name,commands[i].name,strlen(commands[i].name))) {
			which=commands[i].type;
			break;
		}
	}
	return which;

}

static int truncate_filename(char *out, char *path, char *in) {

	int truncated=0;
	int last_slash=0;
	int i;

	for(i=0;i<strlen(in);i++) {
		if (in[i]=='/') last_slash=i+1;
	}

	strncpy(path,in,last_slash);

	/* Truncate filename if too long */
	if (strlen(in+last_slash)>PRODOS_FILENAME_LEN) {
		fprintf(stderr,"Warning!  Truncating %s to 15 chars\n",in+last_slash);
		truncated=1;
	}
	strncpy(out,in+last_slash,PRODOS_FILENAME_LEN);
	out[PRODOS_FILENAME_LEN]='\0';

	return truncated;
}


int main(int argc, char **argv) {

	char image[BUFSIZ];
	char type[4];
	int prodos_fd=0,i;
	int interleave=PRODOS_INTERLEAVE_PRODOS,arg_interleave=0;
	int image_offset=0;

	int command,file_exists;
	char temp_string[BUFSIZ];
	char apple_filename[31],new_filename[31];
	char apple_path[BUFSIZ];
	char local_filename[BUFSIZ];
	char *result_string;
	int always_yes=0;
	char *temp,*endptr;
	int c;
	int inode;
	int address=0, length=0;
	struct voldir_t voldir;
	int dir_block;
	unsigned char header[64];
	int result,num_blocks;


	/* Check command line arguments */
	while ((c = getopt (argc, argv,"a:i:l:t:dhvxy"))!=-1) {
		switch (c) {

		case 'd':
			fprintf(stderr,"DEBUG enabled\n");
			debug=1;
			break;
		case 'a':
			address=strtol(optarg,&endptr,0);
			if (debug) fprintf(stderr,"Address=%d\n",address);
			break;
		case 't':
			if (strlen(optarg)!=3) {
				fprintf(stderr,"Type %s too long, should be 3 chars\n",optarg);
				return -1;
			}
			for(i=0;i<3;i++) {
				type[i]=toupper(optarg[i]);
			}
			type[3]=0;
			if (debug) fprintf(stderr,"Type=%s\n",type);
			break;
		case 'i':
			if (!strncmp(optarg,"prodos",6)) {
				arg_interleave=1;
			}
			if (!strncmp(optarg,"dos33",5)) {
				arg_interleave=2;
			}
			if (debug) fprintf(stderr,"Interleave=%d\n",arg_interleave);
			break;
		case 'l':
			length=strtol(optarg,&endptr,0);
			if (debug) fprintf(stderr,"Length=%d\n",address);
			break;
		case 'v':
			display_help(argv[0],1);
			return 0;
		case 'h': display_help(argv[0],0);
			return 0;
		case 'x':
			ignore_errors=1;
			break;
		case 'y':
			always_yes=1;
			break;
		}
	}

	if (optind==argc) {
		fprintf(stderr,"ERROR!  Must specify disk image!\n\n");
		return -1;
	}

	/* get argument 1, which is image name */
	strncpy(image,argv[optind],BUFSIZ-1);
	prodos_fd=open(image,O_RDWR);
	if (prodos_fd<0) {
		fprintf(stderr,"Error opening disk_image: %s\n",image);
		return -1;
	}

	/* Try to autodetect if 2mg format */
	read(prodos_fd,header,64);
	result=read_2mg_header(header,&num_blocks,&interleave,
			&image_offset,debug);
	if (result==PRODOS_ERROR_BAD_MAGIC) {
		/* not 2mg, try to guess otherwise */

		if (debug) {
			printf("checking extension: %s\n",&image[strlen(image)-4]);
		}

		/* Try to autodetect interleave based on filename */
		if (strlen(image)>4) {

			if (!strncmp(&image[strlen(image)-4],".dsk",4)) {
				if (debug) printf("Detected DOS33 interleave\n");
				interleave=PRODOS_INTERLEAVE_DOS33;
			}

			else if (!strncmp(&image[strlen(image)-3],".po",3)) {
				if (debug) printf("Detected ProDOS interleave\n");
				interleave=PRODOS_INTERLEAVE_PRODOS;
			}
		}
	}

	/* override inteleave if set */
	if (arg_interleave) {
		interleave=arg_interleave-1;
	}

	prodos_init_voldir(prodos_fd,&voldir,interleave,image_offset);

	/* Move to next argument */
	optind++;

	if (optind==argc) {
		fprintf(stderr,"ERROR!  Must specify command!\n\n");
		return -2;
	}

	/* Grab command */
	strncpy(temp_string,argv[optind],BUFSIZ-1);

	/* Make command be uppercase */
	for(i=0;i<strlen(temp_string);i++) {
		temp_string[i]=toupper(temp_string[i]);
	}

	/* Move to next argument */
	optind++;

	command=lookup_command(temp_string);

	switch(command) {

	case COMMAND_UNKNOWN:
		fprintf(stderr,"ERROR!  Unknown command %s\n",temp_string);
		fprintf(stderr,"\tTry \"%s -h\" for help.\n\n",argv[0]);
		goto exit_and_close;
		break;

	/* Load a file from disk image to local machine */
	case COMMAND_LOAD:

		/* check and make sure we have apple_filename */
		if (argc==optind) {
			fprintf(stderr,"Error! Need apple file_name\n");
			fprintf(stderr,"%s %s LOAD apple_filename\n",
				argv[0],image);
			goto exit_and_close;
		}

		truncate_filename(apple_filename,apple_path,argv[optind]);

		if (debug) printf("\tApple filename: %s, path %s\n",
			apple_filename,apple_path);

		/* get output filename */
		optind++;
		if (argc>optind) {
			if (debug) printf("Using %s for filename\n",
						local_filename);
			strncpy(local_filename,argv[optind],BUFSIZ-1);
		}
		else {
			if (debug) printf("Using %s for filename\n",
						apple_filename);
			strncpy(local_filename,apple_filename,31);
		}

		if (debug) printf("\tOutput filename: %s\n",local_filename);

		if (apple_path[0]==0) {
			dir_block=PRODOS_VOLDIR_KEY_BLOCK;
		}
		else {
			dir_block=prodos_get_directory(&voldir,apple_path);
			if (dir_block<0) {
				fprintf(stderr,"Error, couldn't open directory %s\n",argv[optind]);
				return -1;
			}
		}

		/* get the voldir/entry for file */
		inode=prodos_lookup_file(&voldir,
				dir_block,apple_filename);

		if (inode<0) {
			fprintf(stderr,"Error!  %s not found!\n",
				apple_filename);
			goto exit_and_close;
		}


		/* Load the file */
		prodos_load_file(&voldir,inode,local_filename);

		break;

	case COMMAND_CATALOG:

		if (argc>optind) {
			dir_block=prodos_get_directory(&voldir,argv[optind]);
			if (dir_block<0) {
				fprintf(stderr,"Error, couldn't open directory %s\n",argv[optind]);
				return -1;
			}
			prodos_catalog(&voldir,dir_block,argv[optind]);
		}
		else {
			/* use root dir */
			dir_block=PRODOS_VOLDIR_KEY_BLOCK;
			prodos_catalog(&voldir,dir_block,NULL);
		}



		break;

	case COMMAND_SAVE:

		if (debug) printf("\ttype=%s\n",type);

		if (argc==optind) {
			fprintf(stderr,"Error! Need file_name\n");

			fprintf(stderr,"%s %s SAVE "
					"file_name apple_filename\n\n",
					argv[0],image);

			goto exit_and_close;
		}

		strncpy(local_filename,argv[optind],BUFSIZ-1);
		optind++;

		if (debug) printf("\tLocal filename: %s\n",local_filename);

		if (argc>optind) {
			/* apple filename specified */
			truncate_filename(apple_filename,apple_path,argv[optind]);
		}
		else {
			/* If no filename specified for apple name    */
			/* Then use the input name.  Note, we strip   */
			/* everything up to the last slash so useless */
			/* path info isn't used                       */

			temp=local_filename+(strlen(local_filename)-1);

			while(temp!=local_filename) {
				temp--;
				if (*temp == '/') {
					temp++;
					break;
				}
			}

			truncate_filename(apple_filename,apple_path,temp);
		}

		if (apple_path[0]==0) {
			dir_block=PRODOS_VOLDIR_KEY_BLOCK;
		}
		else {
			dir_block=prodos_get_directory(&voldir,apple_path);
			if (dir_block<0) {
				fprintf(stderr,"Error, couldn't open directory %s\n",argv[optind]);
				return -1;
			}
		}

		if (debug) printf("\tApple filename: %s, path: %s\n",
			apple_filename,apple_path);

		file_exists=prodos_check_file_exists(&voldir,
			dir_block,apple_filename);

		if (file_exists>=0) {
			fprintf(stderr,"Warning!  %s exists!\n",apple_filename);
			if (!always_yes) {
				printf("Over-write (y/n)?");
				result_string=fgets(temp_string,BUFSIZ,stdin);
				if ((result_string==NULL) || (temp_string[0]!='y')) {
					printf("Exiting early...\n");
					goto exit_and_close;
				}
			}
			fprintf(stderr,"Deleting previous version...\n");
			prodos_delete_file(&voldir,dir_block,apple_filename);
		}

		prodos_add_file(&voldir,dir_block,type,
				address, length,
				local_filename,apple_filename);

		break;


	case COMMAND_DELETE:

		if (argc==optind) {
			fprintf(stderr,"Error! Need file_name\n");
			fprintf(stderr,"%s %s DELETE apple_filename\n",
				argv[0],image);
			goto exit_and_close;
		}

		truncate_filename(apple_filename,apple_path,argv[optind]);

		if (apple_path[0]==0) {
			dir_block=PRODOS_VOLDIR_KEY_BLOCK;
		}
		else {
			dir_block=prodos_get_directory(&voldir,apple_path);
			if (dir_block<0) {
				fprintf(stderr,"Error, couldn't open directory %s\n",argv[optind]);
				return -1;
			}
		}

		file_exists=prodos_check_file_exists(&voldir,
				dir_block,apple_filename);
		if (file_exists<0) {
			fprintf(stderr, "Error!  File %s does not exist\n",
					apple_filename);
			goto exit_and_close;
		}
		prodos_delete_file(&voldir,dir_block,apple_filename);

		break;

	case COMMAND_DUMP:
		printf("Dumping %s!\n",image);
		prodos_dump(&voldir);
		break;

	case COMMAND_SHOWFREE:
		printf("Showing Free %s!\n",image);
		prodos_showfree(&voldir);
		break;

	case COMMAND_RENAME:
		/* check and make sure we have apple_filename */
		if (argc==optind) {
			fprintf(stderr,"Error! Need two filenames\n");
			fprintf(stderr,"%s %s RENAME apple_filename_old "
				"apple_filename_new\n",
				argv[0],image);
	     		goto exit_and_close;
		}

		/* Truncate filename if too long */
		truncate_filename(apple_filename,apple_path,argv[optind]);
		optind++;

		if (argc==optind) {
			fprintf(stderr,"Error! Need two filenames\n");
			fprintf(stderr,"%s %s RENAME apple_filename_old "
				"apple_filename_new\n",
				argv[0],image);
	     		goto exit_and_close;
		}

		truncate_filename(new_filename,apple_path,argv[optind]);

		if (apple_path[0]==0) {
			dir_block=PRODOS_VOLDIR_KEY_BLOCK;
		}
		else {
			dir_block=prodos_get_directory(&voldir,apple_path);
			if (dir_block<0) {
				fprintf(stderr,"Error, couldn't open directory %s\n",argv[optind]);
				return -1;
			}
		}

		/* get the entry/track/sector for file */
		file_exists=prodos_check_file_exists(&voldir,
					dir_block,apple_filename);
		if (file_exists<0) {
			fprintf(stderr,"Error!  %s not found!\n",
							apple_filename);
			goto exit_and_close;
		}

		prodos_rename_file(&voldir,apple_filename,new_filename);

		break;


	/* Change the volume name */
	case COMMAND_VOLNAME:


		/* check and make sure we have a volume name */
		if (argc==optind) {
			fprintf(stderr,"Error! Need apple volume_name\n");
			fprintf(stderr,"%s %s VOLUME volume_name\n",
				argv[0],image);
			goto exit_and_close;
		}

		prodos_change_volume_name(&voldir,argv[optind]);

		break;

	default:
		fprintf(stderr,"Sorry, unsupported command %s\n\n",temp_string);
		goto exit_and_close;
	}

exit_and_close:
	close(prodos_fd);

	return 0;
}
