/*
    MOVE v2.5 Copyright (C) Kai-Hong Chen 2003
         v2.0 Copyright (C) Joe Cosentino 1997-2002

    The original source code is from Joe Cosentino,
    I rewrite it in Sphinx C-- for size and additional functions.

    The syntax is more free and usable now.

     - If source is a directory, all files in it will be moved
     - If source path and destination path are the same, just try to rename
     - Destination can be a new directory
     - When moving single file, destination can be a new filename
     - Remove unnecessary '\' at the tail of path

    Since v2.3, support long file names when LFN API is found. 
    Work in DOS box, or pure DOS with long file name driver.
    * DosLfn v0.32n and LFNDOS v1.06 is tested.

    History:

     V2.5
     - List multi-files to move
     - Auto create full path of destination
     - Rewrite /V option
     - Correct some small bugs

     V2.4
     - Add option /S to move directories and subdirectories
     - Correct some bugs

     V2.3
     - Rewrite file opreation routines to support long file name
     - Rewrite fprint.h-- for best suitable, cprintf() added
     - Add options /N and /Y

     V2.2
     - Add option to verify by dos
     - Correct some small bugs
     - Rewrite some code for size

     v2.1
     - The first version written in C--
*/

#parsecommandline TRUE
#define _USELONGNAME_ TRUE
#define _CHECKLONGNAMESUPPORT_ TRUE
#include "String.hmm"
#include "fprint.h--"
#include "dos.h--"

#define MOVEBUFFERSIZE 4000h

#define F_NOEXIST       01
#define F_WILDCARD      02
#define F_FILE          04
#define F_DIR           08
#define F_ROOT          16

#define NULL 0

char isOverwriteAll = 0,opt_verify = 0,opt_yes = 0,isDestFileExist;
char buffer[MOVEBUFFERSIZE];
char buffer1[MOVEBUFFERSIZE];

:void fastcall copynear(DI,SI,CX)
{
	$repe $movsb
}

:void fastcall writeln()
{
	@putch(0Dh);
	@putch(0Ah);
}

:void fastcall fixpath(BX)
{
	WHILE(DSBYTE[BX] != 0)
	{
		IF(DSBYTE[BX + 1] == 0) && (DSBYTE[BX] == '\\') && (DSBYTE[BX - 1] != ':')
			DSBYTE[BX] = 0;

		BX++;
	}
}

:char fastcall choice(CX)
{
	DX = 0;

	for(;;)
	{
		@GETCH();
		IF(AL == 0Dh) && (DL != 0) BREAK;
		IF(AL >= 'a') && (AL <= 'z') AL -= 20h;
		DL = AL;
		IF(STRRCHR(CX,AL) == 0)
		{
			DL = 0;
		}
		ELSE
		{
			IF(DH)
			{
				@putch('\b');
				DH = 0;
			}
			@putch(DL);
			DH = 1;
		}
	}
	@writeln();
	return DL;
}

:byte fastcall checkpathflag(CX)
{
	IF(STRLEN(CX) == 3) return F_ROOT + F_DIR;

	$push cx

	IF(!fileexist(CX)) DH = F_NOEXIST;
	ELSE DH = 0;

	$pop cx

	IF(STRRCHR(CX,'?') != 0) || (STRRCHR(CX,'*') != 0) return DH | F_WILDCARD;

	$push dx
	getfattr(CX);
	$pop dx

	IF(!$test DH,F_NOEXIST)
    {
		IF($test CX,FA_DIREC) DH |= F_DIR;
		ELSE DH |= F_FILE;
	}
	return DH;
}

:byte checksamepath(char *src_pathname,*dest_pathname)
{
	word src_pos,dest_pos;

	src_pos = STRRCHR(src_pathname,'\\');
	dest_pos = STRRCHR(dest_pathname,'\\');
	DSBYTE[src_pos] = 0;
	DSBYTE[dest_pos] = 0;
	STRCMPI(src_pathname,dest_pathname);
	DSBYTE[src_pos] = '\\';
	DSBYTE[dest_pos] = '\\';

	return AL;
}

void replace_files(char *src_pathname, *src_filemask, *dest_pathname)
{
	char src_filename[MAXPATH],dest_filename[MAXPATH];
	char full_filemask[MAXPATH];
	FFBLK ffblk;

	IF(STRCMPI(dest_pathname,src_pathname) == 0) 
	{
		printf("Source and destination are the same - %s\n",src_pathname);
		return;
	}

	STRCPY(#full_filemask, src_pathname);
	STRCAT(#full_filemask, "\\");
	STRCAT(#full_filemask, src_filemask);

	IF(findfirst(#full_filemask,#ffblk,FA_HIDDEN + FA_SYSTEM + FA_RDONLY + FA_ARCH) == 0)
	do
	{
		IF($test ffblk.ff_attrib,FA_DIREC) CONTINUE;
		// rebuild source filename
		STRCPY(#src_filename, src_pathname);
		STRCAT(#src_filename, "\\");
		STRCAT(#src_filename, #ffblk.ff_name);

		// build destination filename.
		STRCPY(#dest_filename, dest_pathname);
		STRCAT(#dest_filename, "\\");
		STRCAT(#dest_filename, #ffblk.ff_name);

		replace_file(#src_filename,#dest_filename);

	} while(findnext(#ffblk) == 0 );
	findclose();
}


void replace_file(char *src_filename,*dest_filename)
{
	unsigned long free_diskspace,src_filesize,dest_filesize;
	word src_file,dest_file;
	int buffersize;

	isDestFileExist = fileexist(dest_filename);

	IF(!isOverwriteAll) && (isDestFileExist) && (!opt_yes)
	{
		cprintf("Overwrite %s (Yes/No/All/Cancel)?",dest_filename);
		SWITCH(choice("YNAC"))
		{
		CASE 'C':
			EXIT(1);
		CASE 'A':
			isOverwriteAll = TRUE;
		CASE 'Y':
			BREAK;
		CASE 'N':
			return;
		}
	}

	IF(isDestFileExist)
	{
		getfattr(dest_filename);
		IF(!CARRYFLAG) && ($test CL,FA_RDONLY)
		{
			IF(!opt_yes)
			{
				cprintf("File is READ ONLY, are you sure ? (Yes/No)");
				IF(choice("YN") != 'Y') return;
			}
			setfattr(dest_filename,0);
		}

		dest_filesize = getfilesize32(dest_filename);
	}

	printf("%s => %s ", src_filename, dest_filename);

	src_filesize = getfilesize32(src_filename);
	free_diskspace = getdfreespace(DSBYTE[dest_filename]);

	IF(src_filesize > free_diskspace + dest_filesize)
	{
		printf("[Insufficient disk space]\n");
		EXIT(1);
	}

	// check for just rename
	IF(checksamepath(src_filename,dest_filename) == 0)
	{
		IF(isDestFileExist) deletefile(dest_filename);
		@rename(src_filename,dest_filename);
		IF(CARRYFLAG)
		{
			printf("[Unable to rename source]\n");
			return;
		}
		printf("[ok]\n");
		return;
	}

    // open source file.
	IF(src_file = @fopen(src_filename,F_READ) == NULL)
	{
		printf("[Unable to open source]\n");
		return;
	}

	// create destination file
	IF(dest_file = @fcreate(dest_filename,0) == NULL)
	{
		printf("[Cannot create destination file]\n");
		fclose(src_file);
		return;
	}

	// copy file data
	WHILE(buffersize = @fread(src_file,#buffer,MOVEBUFFERSIZE) > 0)
	{
		@fwrite(dest_file,#buffer,buffersize);
		IF(CARRYFLAG) || (AX != buffersize)
		{
			printf("[Error writing destination file]\n");
			fclose(src_file);
			fclose(dest_file);
			return;
		}
	}

	// copy file timestamp
	@getftime(src_file);
	@setftime(dest_file,CX,DX);

	// close files
	fclose(src_file);
	fclose(dest_file);

	// copy file attributes
	getfattr(src_filename);
	setfattr(dest_filename,CX);

	// verify file
	IF(opt_verify)
	{
		src_file = fopen(src_filename,F_READ);

		IF(dest_file = fopen(src_filename,F_READ) == NULL)
		{
		VERIFYOPENERROR:
			printf("[Error verifying]\n");
			fclose(src_file);
			return;
		}	

		WHILE(buffersize = @fread(src_file,#buffer,MOVEBUFFERSIZE) > 0)
		{
			@fread(dest_file,#buffer1,buffersize);
			IF(CARRYFLAG) || (AX != buffersize) 
			{
			VERIFYERROR:
				fclose(dest_file);
				GOTO VERIFYOPENERROR;
			}
			SI = #buffer;
			DI = #buffer1;
			CX = buffersize;
			$repe $cmpsb
			IF(CX) GOTO VERIFYERROR;
		}
		
	}

	// delete file
	setfattr(src_filename,0);
	deletefile(src_filename);

	printf("[ok]\n");
}

byte message[] =	"MOVE v2.5 Copyright (C) Kai-Hong Chen 2003\n"
					"Move files to another location\n\n"
					"Usage: MOVE source [source ...] [destination]\n"
					"  source      File you want to move, wildcard accepted\n"
					"  destination Where you want to move to, default current path\n\n"

					"  /S          Move directories and subdirectories\n"
					"  /V          Verifies each file\n"
					"  /Y          Always answer YES to questions\n"
					"  /N          Never use long file name API\n\n"

					"Long file name API state : ";

int main()
{
	word fileargc = 0,i;
	word fileargv[255];
	char src_pathname[MAXPATH];
	char src_filemask[MAXPATH],dest_pathname[MAXPATH];
	byte opt_help = 0,opt_subdir = 0;
	byte moveSingleFile = 0;
	byte src_flag,dest_flag;
	word src_path,dest_path;

	@initLongnameApi();

	for(DX = 0;@PARAMCOUNT() > DX;DX++)
	{
		SI = @PARAMSTR(DX);
		IF(DSBYTE[SI] == '/')
		{
			SWITCH(DSBYTE[SI + 1])
			{
			CASE 'V':
			CASE 'v':
				opt_verify++;
				BREAK;
			CASE 'N':
			CASE 'n':
				longnameApi = 0;
				BREAK;
			CASE 'Y':
			CASE 'y':
				opt_yes++;
				BREAK;
			CASE 'S':
			CASE 's':
				opt_subdir++;
				BREAK;
			CASE '?':
			CASE 'h':
			CASE 'H':
				opt_help++;
				BREAK;
			default:
				printf("Invalid switch - %s\n", SI);
				EXIT(1);
			}
		}
		ELSE
		{
			CX = SI;
			fileargv[fileargc] = CX;
			fileargc++;
		}
	}

	IF(opt_help) || (fileargc < 1)
	{
		printf(#message);
		IF(@isLongnameSupport()) printf("OK\n");
		ELSE printf("disabled\n");
		EXIT(0);
	}

	// canonicalize destination
	IF(fileargc == 1)
	{
		truename(".",#dest_pathname); // get current path
		fileargc++;
	}
	ELSE
	{
		truename(fileargv[fileargc - 1],#dest_pathname);
		IF(CARRYFLAG)
		{
			printf("Invalid component or drive letter - %s\n");
			EXIT(1);
		}
	}
	fixpath(#dest_pathname);
	dest_flag = checkpathflag(#dest_pathname);

	IF($test dest_flag,F_WILDCARD)
	{
		printf("Destination must be a single file or directory\n");
		EXIT(1);
	}

	IF($test dest_flag,F_ROOT)
	{
		dest_pathname[2] = 0;
	}

	for(i = 0;i + 1 < fileargc;i++)
	{
		// canonicalize source
		truename(fileargv[i],#src_pathname);
		IF(CARRYFLAG)
		{
			printf("Invalid component or drive letter - %s\n",fileargv[i]);
			continue;
		}
		fixpath(#src_pathname);
		src_flag = checkpathflag(#src_pathname);

		IF(src_flag == F_NOEXIST)
		{
			printf("Cannot move %s - No such file or directory\n",fileargv[i]);
			continue;
		}

		IF($test src_flag,F_ROOT)
		{
			src_pathname[2] = 0;
		}

		IF($test src_flag,F_DIR)
		{
			STRCPY(#src_filemask,"*.*");
		}

		IF($test src_flag,F_FILE) || ($test src_flag,F_WILDCARD)
		{
			SI = STRRCHR(#src_pathname,'\\');
			DSBYTE[SI] = 0;
			STRCPY(#src_filemask,SI + 1);
		}

		IF($test dest_flag,F_FILE)
		{
			IF($test src_flag,F_WILDCARD) || ($test src_flag,F_DIR) || (fileargc > 2)
			{
				printf("Cannot move multiple files to a single file\n");
				EXIT(1);
			}
			moveSingleFile++;
		}

		IF(dest_flag == F_NOEXIST)
		{
			IF($test src_flag,F_WILDCARD) || ($test src_flag,F_DIR) || (fileargc > 2)
			{
				IF(opt_yes) GOTO MAKEDIR;
					cprintf("Make directory \"%s\"? [YN]",#dest_pathname);
					IF(choice("YN") == 'Y')
					{
					MAKEDIR:
						mkpath(#dest_pathname);
						dest_flag &= !F_NOEXIST;
					}
					ELSE EXIT(1);
			}
			ELSE moveSingleFile++;
		}

		IF(moveSingleFile)
		{
			STRCAT(#src_pathname, "\\");
			STRCAT(#src_pathname, #src_filemask);

			IF(STRCMPI(#dest_pathname,#src_pathname) == 0)
			{
				printf("Source and destination are the same - %s\n",#src_pathname);
				CONTINUE;
			}

			replace_file(#src_pathname,#dest_pathname);
		}
		ELSE IF(!opt_subdir)
		{
			replace_files(#src_pathname, #src_filemask, #dest_pathname);
		}
		ELSE
		{
			{
				//doDirectory(#src_pathname);
				STRCPY(#save_dest_pathname,#dest_pathname);
				replace_files(#src_pathname, #src_filemask, #dest_pathname);
				walkDirectory(#src_pathname, #src_filemask, #dest_pathname);
				rmdir(#src_pathname);
			}
		}

	}
	EXIT(0);
}

:void doDirectory(char *dir)
{
	printf("%s\n",dir);
}

char save_dest_pathname[MAXPATH];
void walkDirectory(char *startDir, *src_filemask, *dest_pathname)
{
	FFBLK ffblk;
	char pathname[MAXPATH];

	STRCPY(#pathname, startDir);
	STRCAT(#pathname, "\\*.*");

	CX = FA_DIREC * 100h + FA_DIREC + FA_HIDDEN + FA_SYSTEM;
	if(findfirst(#pathname,#ffblk,CX) == 0)
    do
    {
		IF(DSBYTE[#ffblk.ff_name] == '.') continue;
		
		STRCPY(#pathname, startDir);
		STRCAT(#pathname, "\\");
		STRCAT(#pathname, #ffblk.ff_name);
		truename(#pathname,#pathname);

		STRCAT(dest_pathname, "\\");
		STRCAT(dest_pathname,#ffblk.ff_name);

		IF(STRCMPI(#save_dest_pathname,#pathname) == 0)
		{
			BX = STRRCHR(dest_pathname,'\\');
			DSBYTE[BX] = 0;
			continue;
		}
//		doDirectory(#pathname);
		IF(!fileexist(dest_pathname)) mkdir(dest_pathname);
		IF(getfattr(dest_pathname) & FA_DIREC) 
			replace_files(#pathname, src_filemask, dest_pathname);
		walkDirectory(#pathname,src_filemask, dest_pathname);
		rmdir(#pathname);

		BX = STRRCHR(dest_pathname,'\\');
		DSBYTE[BX] = 0;

	} while(findnext(#ffblk) == 0);
	findclose();
}

void mkpath(char *path)
{
	int levelcount = 0;

	WHILE(!fileexist(path)) && (STRLEN(path) > 2)
	{
		BX = STRRCHR(path,'\\');
		DSBYTE[BX] = 0;
		levelcount++;
	}

	LOOPNZ(levelcount)
	{
		BX = path;
		BX += STRLEN(BX);
		DSBYTE[BX] = '\\';
		mkdir(path);
	}
}