#!/usr/bin/env python3

from subprocess import run, PIPE, DEVNULL
from sys import argv
from time import sleep
import datetime
from os import path

import tempfile

# oldrun = run
# def run(*args, **kwargs):
# 	print('CMD', *args)
# 	return oldrun(*args, **kwargs)




# I am now modifying this to accept *no* arguments,
# instead operating on the CWD.


# A couple of useful HFSUTILS wrappers

def h_topdir():
	rc = 0
	while rc == 0:
		rc = run(['hcd','::'], stderr=DEVNULL).returncode

def h_reccd(path_comps):
	h_topdir()
	for p in path_comps:
		run(['hcd', p], check=True)





# All relative to the CWD, of course:
srcimg = 'SourceForEmulator.dmg'
label = 'Src'





# Read and sanitise the arguments. Outputs: "dirs" and "cmds" (both lists, may be empty)
# Dirs are in /full/path/without/trailing/slash form, thanks to os.path.abspath

if not path.exists(srcimg):
	run(['touch', srcimg])
	run(['xattr', '-w', 'com.apple.metadata:com_apple_backup_excludeItem', 'com.apple.backupd', srcimg])
	run(['dd', 'if=/dev/zero', 'of='+srcimg, 'bs=1048576', 'count='+str(256)], stdout=DEVNULL, stderr=DEVNULL, check=True)
	run(['hformat', '-l', label, srcimg], stdout=DEVNULL, check=True)
	run(['humount'], check=True)




# Now run rsync and see what we can get out of it! (Should at least reconsider rsync policy!)
# Rsync should update any changed file
# Potential problem: need to remove ._ thingummyjigs!

rsync_opts = [
	'--dry-run',					# Ask rsync what it *would* do if macOS
	'--itemize-changes',			# 	hadn't dropped R/W HFS support.
	'--exclude', '.*',				# Down with dotfiles.
	'--exclude', srcimg,
	'--exclude', 'Desktop DB',
	'--exclude', 'Desktop DF',
	'--exclude', 'Desktop Folder',
	'--exclude', 'Trash',
	'--exclude', 'TheVolumeSettingsFolder',
	'--exclude', 'TheFindByContentFolder',
	'--recursive',
	'-tX',							# consider times and xattrs
	'--delete',						# may delete things on the vMac drive
]

try:
	cmdresult = run(['hdiutil', 'attach', '-nobrowse', srcimg], stdout=PIPE, check=True).stdout.decode('ascii')
	mountpoint = next(x for x in cmdresult.split() if x.startswith('/') and not x.startswith('/dev'))
	mountpoint = mountpoint.rstrip('/') # try to deal in clean paths

	rsync_list = [] # (local_base_path, relative_path_components, rsync_code) tuple

	# rsync /src/dir/without/trailing/slash/DIRNAME /dest/dir/with/trailing/slash/
	# results in /dest/dir/with/trailing/slash/DIRNAME
	rsync = run(['rsync', *rsync_opts, './', mountpoint+'/'], stdout=PIPE, check=True)
	new_rsync_lines = rsync.stdout.decode('utf8').splitlines()

	for new_rsync_line in new_rsync_lines:
		spcidx = new_rsync_line.index(' ')
		code_part = new_rsync_line[:spcidx]
		path_part = new_rsync_line[spcidx+1:].lstrip()

		if '._' in path_part:
			raise ValueError('AppleDouble-style name detected: %s' % path_part)

		rsync_list.append((code_part, path_part))

finally:
	run(['hdiutil', 'detach', mountpoint], check=True, stdout=DEVNULL)



# for rsync_code, path in rsync_list:
# 	# Get rid of the silly AppleDouble prefix
# 	a, b = path.splitext(rsync_path)
# 	if b.startswith('._'):
# 		b = b[2:]
# 		path = path.join(a, b)


# 	try:




run(['hmount', srcimg], stdout=DEVNULL, check=True)

try:
	for rsync_code, rsync_path in rsync_list:
		# print([local_dir, rsync_code, rsync_path])

		# Suggest: replace with code to make fuck/marry/kill plan for each file


		# first, parse the rsync commands!
		if rsync_code.startswith('*'):
			if rsync_code == '*deleting':
				print('-', rsync_path)

				# similar to the folder creation code below
				if rsync_path.endswith('/'):
					cmdname = 'hrmdir'
				else:
					cmdname = 'hdel'

				a, b = path.split(rsync_path.rstrip('/'))
				hfs_dir_comps = a.split('/') if a else []
				hfs_deleteme = b

				h_reccd(hfs_dir_comps)
				run([cmdname, hfs_deleteme], check=True)

			else:
				raise ValueError('Unknown extended rsync code: %s' % rsync_code)

		elif rsync_code.startswith('.'):
			pass

		else:
			utype, ftype, cdiff, sdiff, tdiff, pdiff, odiff, gdiff, *_ = rsync_code

			if utype == '>':
				if ftype == 'f':
					print('+', rsync_path)

					hfs_dir = ':' + path.join(path.dirname(rsync_path), '').replace(*'/:')

					with tempfile.NamedTemporaryFile(prefix='/tmp/', suffix='.bin') as f:
						macbin_tmp = f.name

					h_topdir()
					run(['macbinary', 'encode', '--overwrite', '-o', macbin_tmp, rsync_path], check=True)
					run(['hcopy', '-m', macbin_tmp, hfs_dir], check=True)

				else:
					raise ValueError()

			elif utype == 'c':
				if ftype == 'd':
					a, b = path.split(rsync_path.rstrip('/'))
					hfs_dir_comps = a.split('/') if a else []
					hfs_mkdir_name = b

					h_reccd(hfs_dir_comps)
					run(['hmkdir', hfs_mkdir_name], check=True)

				else:
					raise ValueError()

			elif utype == '.':
				pass

			else:
				raise ValueError('Unknown rsync update code: %s' % rsync_code)

finally:
	run(['humount'], check=True)




# YXcstpoguax  path/to/file
# |||||||||||
# `----------- the type of update being done::
#  ||||||||||   <: file is being transferred to the remote host (sent).
#  ||||||||||   >: file is being transferred to the local host (received).
#  ||||||||||   c: local change/creation for the item, such as:
#  ||||||||||      - the creation of a directory
#  ||||||||||      - the changing of a symlink,
#  ||||||||||      - etc.
#  ||||||||||   h: the item is a hard link to another item (requires --hard-links).
#  ||||||||||   .: the item is not being updated (though it might have attributes that are being modified).
#  ||||||||||   *: means that the rest of the itemized-output area contains a message (e.g. "deleting").
#  ||||||||||
#  `---------- the file type:
#   |||||||||   f for a file,
#   |||||||||   d for a directory,
#   |||||||||   L for a symlink,
#   |||||||||   D for a device,
#   |||||||||   S for a special file (e.g. named sockets and fifos).
#   |||||||||
#   `--------- c: different checksum (for regular files)
#    ||||||||     changed value (for symlink, device, and special file)
#    `-------- s: Size is different
#     `------- t: Modification time is different
#      `------ p: Permission are different
#       `----- o: Owner is different
#        `---- g: Group is different
#         `--- u: The u slot is reserved for future use.
#          `-- a: The ACL information changed
