/*
** 1998-05-30 -	Implementation of the built-in MOVE command, which is used to, er, move
**		files and directories. It might become incredibly involved. We'll see
**		about that.
** 1998-09-12 -	Did changes in command arguments to ease implementation of MOVEAS, also
**		exported all the move_XXX() functions, as the cmd_copy module does.
** 1999-03-06 -	Adapted for new selection/dirrow representations.
*/

#include "gentoo.h"

#include "errors.h"
#include "dirpane.h"
#include "fileutil.h"
#include "overwrite.h"
#include "progress.h"

#include "cmd_copy.h"
#include "cmd_move.h"

#define	CMD_ID	"move"

/* ----------------------------------------------------------------------------------------- */

/* 1998-09-12 -	Modified: now <to> is the full destination name. */
int move_file(MainInfo *min, char *from, char *to, struct stat *sstat)
{
	if(copy_file(min, from, to, sstat))
		unlink(from);
	return errno == 0;
}

/* 1998-09-12 -	Modified: now <to> is the complete destination name. */
int move_device(MainInfo *min, char *from, char *to, struct stat *sstat)
{
	if(copy_device(min, from, to, sstat))
		unlink(from);

	return errno == 0;
}

/* 1998-06-07 -	Move a symbolic link. Will check if the link is "explicit", i.e. if it contains a full path
**		name, and then compare that name to the directory it is residing in. On match, a translation
**		is done so that the new link (created at the destination) is correct.
** 1998-09-12 -	Modified; now <to> is the full destination name, rather than just the dir.
*/
int move_link(MainInfo *min, char *from, char *to)
{
	if(copy_link(min, from, to))
		unlink(from);

	return errno == 0;
}

/* 1998-06-07 -	Move a directory full of stuff. Does not simply call copy_dir from the cmd_copy module,
**		since that would require also calling del_dir (from cmd_delete). I feel that is just too
**		ineffecient, having to traverse twice.
** 1998-09-12 -	Modified: now <to> is the complete destination name, since this is the new deal.
*/
int move_dir(MainInfo *min, char *from, char *to)
{
	char		old_dir[PATH_MAX];
	DIR		*dir;
	struct dirent	*de;
	struct stat	stat;

	if(lstat(from, &stat) != 0)
	{
		err_set(min, errno, CMD_ID, from);
		return 0;
	}

	if(fut_cd(from, old_dir, sizeof old_dir))
	{
		if(mkdir(to, stat.st_mode | S_IWUSR) == 0)
		{
			if((dir = opendir(".")) != NULL)
			{
				while(!errno && (de = readdir(dir)) != NULL)
				{
					if(!min->cfg.dir_filter(de->d_name))
						continue;
					if(lstat(de->d_name, &stat) == 0)
					{
						char	dest2[PATH_MAX];

						sprintf(dest2, "%s/%s", to, de->d_name);
						if(S_ISDIR(stat.st_mode))
							move_dir(min, de->d_name, dest2);
						else if(S_ISREG(stat.st_mode))
							move_file(min, de->d_name, dest2, &stat);
						else if(S_ISLNK(stat.st_mode))
							move_link(min, de->d_name, dest2);
						else if(S_ISBLK(stat.st_mode) || S_ISCHR(stat.st_mode))
							move_device(min, de->d_name, dest2, &stat);
						if(!errno)
							rewinddir(dir);
					}
				}
				closedir(dir);
			}
		}
		if(!errno && fut_cd(old_dir, NULL, 0))
			rmdir(from);
	}
	if(errno)
		err_set(min, errno, CMD_ID, from);

	return errno == 0;
}

/* 1998-07-11 -	Bug fix: always failed after first file when moving on the same device. The
**		error reporting was also slightly broken.
*/
int cmd_move(MainInfo *min, DirPane *src, DirPane *dst, CmdArg *ca)
{
	char		old_path[PATH_MAX], dest[PATH_MAX], *name = NULL;
	guint		count = 0;
	GSList		*slist, *iter;
	mode_t		mode;
	OvwRes		ores;

	err_clear(min);

	if((src == NULL) || (dst == NULL))
		return 1;
	if(!fut_cd(src->dir.path, old_path, sizeof old_path))
		return 0;

	if((slist = dp_get_selection(src)) == NULL)
		return 1;

	ovw_overwrite_begin(min, "\"%s\" already exists - continue with move?", 0UL);
	pgs_progress_begin(min, "Move", PFLG_COUNT_RECURSIVE | PFLG_ITEM_VISIBLE | PFLG_BYTE_VISIBLE);
	for(iter = slist; !errno && (iter != NULL); iter = g_slist_next(iter))
	{
		name = DP_SEL_NAME(iter);
		sprintf(dest, "%s/%s", dst->dir.path, name);
		ores = ovw_overwrite_file(min, dest, dp_full_name(src, DP_SEL_INDEX(src, iter)));
		if(ores == OVW_SKIP)
			continue;
		else if(ores == OVW_CANCEL)
			break;

		mode = DP_SEL_LSTAT(iter).st_mode;
		if(S_ISLNK(mode))
			move_link(min, DP_SEL_NAME(iter), dest);
		else if(rename(name, dest) == 0)		/* Try a direct rename, very quick. */
			count++;
		else if(errno == EXDEV)
		{
			err_clear(min);

			if(S_ISLNK(mode))
				move_link(min, DP_SEL_NAME(iter), dest);
			else if(S_ISREG(mode))
				move_file(min, DP_SEL_NAME(iter), dest, &DP_SEL_LSTAT(iter));
			else if(S_ISBLK(mode) || S_ISCHR(mode))
				move_device(min, DP_SEL_NAME(iter), dest, &DP_SEL_LSTAT(iter));
			else if(S_ISDIR(mode))
				move_dir(min, DP_SEL_NAME(iter), dest);
			else
				fprintf(stderr, "Can't move '%s' -- don't know how!\n", DP_SEL_NAME(iter));
			count += (errno == 0);
		}
		else
			break;
	}
	pgs_progress_end(min);
	ovw_overwrite_end(min);
	if((count > 0) && (errno == 0))			/* If any files were moved, update panes. */
	{
		dp_rescan_dir(src);
		dp_rescan_dir(dst);
		fut_cd(old_path, NULL, 0);
	}
	else if(errno)
		err_set(min, errno, CMD_ID, name);
	err_show(min);

	dp_free_selection(slist);

	return errno == 0;
}
