#!/usr/bin/env python3
"""dotfilemanager.py - a dotfiles manager script. See --help for usage
and command-line arguments.

"""
import os,sys,platform

# TODO: allow setting hostname as a command-line argument also?
try:
    HOSTNAME = os.environ['DOTFILEMANAGER_HOSTNAME']
except KeyError:
    HOSTNAME = platform.node()
HOSTNAME_SEPARATOR = '__'
    
def tidy(d,report=False):
    """Find and delete any broken symlinks in directory d.
    
    Arguments:
    d -- The directory to consider (absolute path)
    
    Keyword arguments:
    report -- If report is True just report on what broken symlinks are
              found, don't attempt to delete them (default: False)
    
    """
    for f in os.listdir(d):
        path = os.path.join(d,f)
        if os.path.islink(path):
            target_path = os.readlink(path)
            target_path = os.path.abspath(os.path.expanduser(target_path))
            if not os.path.exists(target_path):                
                # This is a broken symlink.
                if report:
                    print('tidy would delete broken symlink: %s->%s' % (path,target_path))                    
                else:
                    print('Deleting broken symlink: %s->%s' % (path,target_path))
                    os.remove(path)                    

def get_target_paths(to_dir,report=False):
    """Return the list of absolute paths to link to for a given to_dir.
    
    This handles skipping various types of filename in to_dir and
    resolving host-specific filenames.
    
    """
    paths = []
    filenames = os.listdir(to_dir)
    for filename in filenames:
        path = os.path.join(to_dir,filename)
        if filename.endswith('~'):
            if report:
                print('Skipping %s' % filename)
            continue            
        elif (not os.path.isfile(path)) and (not os.path.isdir(path)):
            if report:
                print('Skipping %s (not a file or directory)' % filename)
            continue
        elif filename.startswith('.'):
            if report:
                print('Skipping %s (filename has a leading dot)' % filename)
            continue
        else:
            if HOSTNAME_SEPARATOR in filename:
                # This appears to be a filename with a trailing
                # hostname, e.g. _muttrc__dulip. If the trailing
                # hostname matches the hostname of this host then we
                # link to it.
                hostname = filename.split(HOSTNAME_SEPARATOR)[-1]
                if hostname == HOSTNAME:
                    paths.append(path)
                else:
                    if report:
                        print('Skipping %s (different hostname)' % filename)
                    continue                    
            else:
                # This appears to be a filename without a trailing
                # hostname.
                if filename + HOSTNAME_SEPARATOR + HOSTNAME in filenames: 
                    if report:
                        print('Skipping %s (there is a host-specific version of this file for this host)' % filename)
                    continue
                else:                                            
                    paths.append(path)    
    return paths

def link(from_dir,to_dir,report=False):
    """Make symlinks in from_dir to each file and directory in to_dir.

    This handles converting leading underscores in to_dir to leading
    dots in from_dir.
    
    Arguments:
    from_dir -- The directory in which symlinks will be created (string,
                absolute path)
    to_dir   -- The directory containing the files and directories that
                will be linked to (string, absolute path)
    
    Keyword arguments:
    report   -- If report is True then only report on the status of
                symlinks in from_dir, don't actually create any new
                symlinks (default: False)
    
    """
    # The paths in to_dir that we will be symlinking to.
    to_paths = get_target_paths(to_dir,report)
    
    # Dictionary of symlinks we will be creating, from_path->to_path
    symlinks = {}
    for to_path in to_paths:
        to_directory, to_filename = os.path.split(to_path)
        # Change leading underscores to leading dots.        
        if to_filename.startswith('_'):
            from_filename = '.' + to_filename[1:]
        else:
            from_filename = to_filename
        # Remove hostname specifiers.
        parts = from_filename.split(HOSTNAME_SEPARATOR)
        assert len(parts) == 1 or len(parts) == 2
        from_filename = parts[0]
        from_path = os.path.join(from_dir,from_filename)        
        symlinks[from_path] = to_path

    # Attempt to create the symlinks that don't already exist.
    for from_path,to_path in list(symlinks.items()):                        
        # Check that nothing already exists at from_path.
        if os.path.islink(from_path):
            # A link already exists.
            existing_to_path = os.readlink(from_path)
            existing_to_path = os.path.abspath(os.path.expanduser(existing_to_path))
            if  existing_to_path == to_path:
                # It's already a link to the intended target. All is
                # well.
                continue
            else:
                # It's a link to somewhere else.
                print(from_path+" => is already symlinked to "+existing_to_path)
        elif os.path.isfile(from_path):
            print("There's a file in the way at "+from_path)
        elif os.path.isdir(from_path):
            print("There's a directory in the way at "+from_path)
        elif os.path.ismount(from_path):
            print("There's a mount point in the way at "+from_path)
        else:
            # The path is clear, make the symlink.
            if report:
                print('link would make symlink: %s->%s' % (from_path,to_path))
            else:
                print('Making symlink %s->%s' % (from_path,to_path))
                os.symlink(to_path,from_path)

def usage():
    return """Usage:

dotfilemanager link|tidy|report [FROM_DIR [TO_DIR]]
    
Commands:
   link -- make symlinks in FROM_DIR to files and directories in TO_DIR
   tidy -- remove broken symlinks from FROM_DIR
   report -- report on symlinks in FROM_DIR and files and directories in TO_DIR
   
FROM_DIR defaults to ~ and TO_DIR defaults to ~/.dotfiles.
   """

if __name__ == "__main__":
    try:
        ACTION = sys.argv[1]
    except IndexError:
        print(usage())
        sys.exit(2)

    try:
        FROM_DIR = sys.argv[2]
    except IndexError:
        FROM_DIR = '~'
    FROM_DIR = os.path.abspath(os.path.expanduser(FROM_DIR))

    if not os.path.isdir(FROM_DIR):
        print("FROM_DIR %s is not a directory!" % FROM_DIR)
        print(usage())
        sys.exit(2)

    if ACTION == 'tidy':
        tidy(FROM_DIR)
    else:

        try:
            TO_DIR = sys.argv[3]
        except IndexError:
            TO_DIR = os.path.join('~','.dotfiles')
        TO_DIR = os.path.abspath(os.path.expanduser(TO_DIR))

        if not os.path.isdir(TO_DIR):
            print("TO_DIR %s is not a directory!" % TO_DIR)
            print(usage())
            sys.exit(2)

        if ACTION == 'link':
            link(FROM_DIR,TO_DIR)
        elif ACTION == 'report':
            link(FROM_DIR,TO_DIR,report=True)
            tidy(FROM_DIR,report=True)
        else:
            print(usage())
            sys.exit(2)