#! /usr/bin/env python3
# -*- encoding: utf-8 -*-

import re, glob, sys, os, os.path

out = sys.stdout.write
err = sys.stderr.write
prg = os.path.basename(sys.argv[0])

encoding = sys.stdout.encoding

arrow = u'\u25b6'

def main(args):
	if len(args) < 2:
		err('%s: v1.3 - (c)2007-2019 André Jonsson\n' % prg)
		err('Usage [-q] <find-regex> <replace> [<files & dirs...>]\n')
		err('   -q     By wewwy, wewwy quiet  (and no questions asked)\n')
		err('   -fx    Overwrite existing files (force-existing)\n')
		return 0

	quiet = False
	if '-q' in args:
		quiet = True
		del args[args.index('-q')]

	forcing = []
	if '-fx' in args:
		forcing.append('existing')
		del args[args.index('-fx')]

	recurse = False
	#if '-r' in args:
	#	recurse = True
	#	del(args[args.index('-r')])

	try:
		pattern = re.compile(args[0])
	except Exception as msg:
		err('%s: Could not compile regular expression: %s\n%s' % (prg, args[0], msg))
		return 1

	replace = args[1]
	files = args[2:]
	if not files:
		files = local_files('.', recurse)
	else:
		tested_files = []
		for name in files:
			if os.path.exists(name):
				tested_files.append(name)
			else:
				err('%s: File does not exist: %s\n' % (prg, name))
		files = tested_files

	if not files:
		err('%s: No files.\n' % prg)
		return 1


	fileList={}
	for f in files:
		fileList[f]=1

	fileList = list(fileList.keys())
	fileList.sort()

	renamings = []
	for name in fileList:
		dirName = os.path.dirname(name)
		name = os.path.basename(name)

		# perform the replacing of the name-part only
		newName = pattern.sub(replace, name)
		
		# check if new name is completely insane
		if newName in ('', '.', '..', '/'):
			err('%s: Can not rename: "%s" %s "%s"  (invalid name)\n' % (prg, name, arrow, newName))
			return 1
		if newName != name:
			# re-attach the dirName to both
			name = os.path.join(dirName, name)
			newName = os.path.join(dirName, newName)
			
			renamings.append((name, newName))
		#else:
		#	err('%s: skipping no change: "%s" %s "%s"' % (prg, name, arrow, newName))

	if not renamings:
		err('%s: Nothing to be renamed.\n' % prg)
		return 0

	if not validateRenames(renamings, forceExisting=('existing' in forcing)):
		return 1

	if not quiet:
		for name, newName in renamings:
			warning = ''
			if os.path.exists(newName):
				warning = ' O!'
			out(' \'%s\' %s \'%s\'%s\n' % (name, arrow, newName, warning))

	if not quiet:
		try:
			answer = input('** Continue with rename? [Y/n] ').strip()
			if answer=='n':
				return 0
		except:
			out('aborted\n')
			return 255

	for rename in renamings:
		name, newName = rename
		try:
			dirName = os.path.dirname(newName)
			if dirName:
				os.makedirs(dirName)
			os.rename(name, newName)
		except Exception as msg:
			err('%s: Renaming "%s" to "%s" failed: %s\n' % (prg, name, newName, msg))

	return 0


def validateRenames(renamings, forceExisting=False):
	# check if renamed files collide
	for idx0 in range(len(renamings)):
		for idx1 in range(len(renamings)):
			if idx0 == idx1:
				continue

			if renamings[idx0][1]==renamings[idx1][1]:
				err('%s: Rename collision: "%s" & "%s" both renaming %s "%s"\n' %
					(prg, renamings[idx0][0], renamings[idx1][0], arrow, renamings[idx0][1]))
				
				return False

	# check if any renamed file collide with any, not yet renamed, original file
	# TODO: re-order ?
	for idx0 in range(len(renamings)):
		for idx1 in range(idx0, len(renamings)):
			if idx0 == idx1:
				continue

			if renamings[idx0][1] == renamings[idx1][0]:
				err('%s: Renaming "%s" %s "%s" would overwrite to-be renamed "%s"\n' %
					(prg, renamings[idx0][0], arrow, renamings[idx0][1], renamings[idx1][0]))

				return False

	# check if renamed files collide with existing files
	if not forceExisting:
		for name, newName in renamings:
			if os.path.exists(newName):
				err('%s: Rename collision with existing file/directory: "%s" %s "%s"\n' %
					(prg, name, arrow, newName))

				return False

	return True


def local_files(root, recursive=False):
	if not recursive:
		return os.listdir(root)
	
	return [
		fn
		for fn in files
		for root, dirs, files in os.walk(root)
		if not fn.startsWith('.')
	]


if __name__=='__main__':
	sys.exit(main(sys.argv[1:]))
