/**********************************************************************
 *
 *	MV.C
 *
 * Unix like file move.
 * Move or rename files in same drive. No disk blocks
 * are moved, only the directory entry. No wild cards
 * are allowed in destination.
 *
 *	J.Ruuth 10-09-1987
 *
 **********************************************************************/

#include <stdio.h>
#include <dir.h>
#include <dos.h>
#include <string.h>
#include <errno.h>
#include <process.h>
#include <conio.h>

typedef enum {
	FALSE = 0,
	TRUE = 1
} BOOLEAN;

extern int	getopt (int argc, char **argv, char *optstring);
extern char *	optarg;
extern int	optind;

extern int	yes_no (char *question, char yes_char, char no_char);

static struct ffblk	ffblk;
static int		exit_code = 0;
static int		force = 0;	/* force flag */
static int		interactive = 0;/* interactive flag */

/**********************************************************************
 *
 *	Dummy function to save little code size
 */
void cdecl	_setenvp (void)
{
}


/**********************************************************************
 *
 *	void helpexit (int error_code)
 *
 * Exit with help message.
 */
static void	helpexit (int error_code)
{
	cputs("Usage:\tmv [options] source ... destination\n\r"
	     "\tIf more than one source, destination must be directory\n\r"
	     "\tOptions: -f\tforce move\n\r"
	     "\t         -i\tinteractive\n\r");
	exit(error_code);
}


/**********************************************************************
 *
 *	void mv_error (void)
 *
 * Display error message.
 */
static void	mv_error (void)
{
	register char	*error_msg;
	
	exit_code = errno;
	switch (errno) {
		case ENOENT:
			error_msg = "Path or file name not found\n\r";
			break;
		case EACCES:
			error_msg = "Permission denied\n\r";
			break;
		case ENOTSAM:
			error_msg = "Not same device\n\r";
			break;
		default:
			error_msg = "Unknown error\n\r";
			break;
	}
	cputs(error_msg);
}


/**********************************************************************
 *
 *	BOOLEAN overwrite (char *dest)
 *
 * Ask user for permission to overwrite existing file.
 */
static BOOLEAN	overwrite (char *dest)
{
	cputs("File ");
	cputs(dest);
	cputs(" exists, overwite");
	return (yes_no (" ?", 'y', 'n'));
}


/**********************************************************************
 *
 *	BOOLEAN mv (char *src, char *dest)
 *
 * Move files. On error, print error message and return FALSE,
 * otherwise return TRUE.
 * Delete destination before move.
 */
static BOOLEAN	mv (char *src, char *dest)
{	
	register char	*error_msg;
	register int	status;
	
	if (interactive) {
		cputs("mv ");
		cputs(src); 
		putch(' '); 
		cputs(dest);
		if (!yes_no (" ?", 'y', 'n'))
			return FALSE;
	}
	if ((status=rename(src, dest)) != 0 && errno==EACCES) {
		if (!interactive && !force && !overwrite(dest))
			return FALSE;
		unlink(dest);
		status = rename(src, dest);
	}
	if (status != 0) {
		mv_error();
		return FALSE;
	}
	return TRUE;
}


/**********************************************************************
 *
 *	int move_to_dir (int srcnum, char *src[], char *dpath)
 *
 * Move files from src to dest. dest must be directory, and
 * it must end with backslash. srcnum is number of sources, src
 * is array of sources.
 * Return number of files moved.
 */

static int	move_to_dir (int srcnum, register char *src[], char *dpath)
{
	int		move_count = 0;
	static char	ddrive[MAXDRIVE],	/* for dest */
			ddir[MAXDIR],
			spath[MAXPATH],		/* for src */
			sdrive[MAXDRIVE],
			sdir[MAXDIR],
			name[MAXFILE],		/* for both */
			ext[MAXEXT];
	
	(void)fnsplit(dpath,ddrive,ddir,NULL,NULL);
	while (srcnum--) {
		if (findfirst(*src, &ffblk, FA_RDONLY)==0) {
			/* get path for source */
			(void)fnsplit(*src,sdrive,sdir,NULL,NULL);
			do {
				(void)fnsplit(ffblk.ff_name,NULL,NULL,name,ext);
				/* merge src and dest path */
				fnmerge(dpath, ddrive, ddir, name, ext);
				fnmerge(spath, sdrive, sdir, name, ext);
				if (mv(spath, dpath))
					++move_count;
			} while (findnext(&ffblk)==0);
		}
		++src;
	}
	return move_count;
}
	
/**********************************************************************
 *
 *	int get_path_type (char *dest)
 *
 * Checks, whether destination is a drive, directory or file, and
 * returns its type.
 * If directory, backslash is added.
 */
static int	get_path_type (char *path)
{
	register int	flag;
	
	if ((flag=fnsplit(path,NULL,NULL,NULL,NULL)) & WILDCARDS)
		return flag;
	if (flag&(FILENAME|EXTENSION))	/* check also . and .. */
		if (findfirst(path,&ffblk,FA_DIREC)==0 && ffblk.ff_attrib&FA_DIREC) {
			(void)strcat(path, "\\");
			return (flag&~(FILENAME|EXTENSION))|DIRECTORY;
		}
	return flag;
}


/**********************************************************************
 *
 *	Main
 */
int cdecl	main (int argc, register char *argv[])
{
	static char	dpath[MAXPATH];
	int		dest_type,opt;
	int		args_left;
	
	while ((opt = getopt(argc, argv, "fFiI")) != EOF) {
		switch (opt) {
			case 'f':
			case 'F':
				force++;
				break;
			case 'i':	
			case 'I':
				interactive++;
				break;
			case '?':
			default:
				cputs("Invalid command line option\n\r");
				helpexit(1);
				break;
		}
	}
	args_left = argc - optind;
	if (args_left < 2)
		helpexit(1);

	/* now argc is index for destination, check valid dest */
	if ((dest_type=get_path_type(strcpy(dpath,argv[argc-1]))) & WILDCARDS) {
		cputs("Invalid destination\n\r\n\r");
		helpexit(1);
	}
	/* simple first: check if dest is file */
	if (dest_type & (FILENAME|EXTENSION)) {
		if (args_left == 2) {
			(void)mv(*(argv+optind), dpath);
			return(exit_code);
		} else
			helpexit(1);
	}
	/* move files to directory */
	if (move_to_dir (args_left, argv+optind, dpath)==0)
		cputs("No files moved\n\r");
	return(exit_code);
}
