#! /usr/bin/python

import logging
import optparse
import os
import shutil
import stat
import sys

import libdelete

logger = logging.getLogger('undelete')
whoami = os.path.basename(sys.argv[0])

def debug_callback(option, opt_str, value, parser):
    """
    An OptionParser callback that enables debugging.
    """
    all_loggers = [logger.name, 'libdelete']
    loggers = [x.strip() for x in value.split(',')]
    if value.lower() == 'all':
        loggers = all_loggers
    else:
        if not set(loggers) == set(all_loggers):
            parser.error('Valid debug targets: {0}'.format(
                    ", ".join(all_loggers)))
    for l in loggers:
        logging.getLogger(l).setLevel(logging.DEBUG)

def ask(question, *args, **kwargs):
    """
    Ask a question, possibly prepended with the name of the program
    and determine whether the user answered in the affirmative
    """
    yes = ('y', 'yes')
    prepend = '' if kwargs.get('nowhoami', False) else "{0}: ".format(whoami)
    try:
        return raw_input("%s%s " % (prepend,
                                    question % args)).strip().lower() in yes
    except KeyboardInterrupt:
        sys.exit(0)

def perror(message, **kwargs):
    """
    Format an error message, log it in the debug log
    and maybe also print it to stderr.
    """
    should_print = kwargs.pop('_maybe', False)
    msg = "{0}: {1}".format(whoami, message.format(**kwargs))
    logger.debug("Error: %s", msg)
    if should_print:
        print >>sys.stderr, msg

def actually_undelete(filename, options):
    undeleted_name = libdelete.undeleted_name(filename)
    logger.debug("actually_undelete(%s)", filename)
    logger.debug("undeleted name: %s", undeleted_name)

    if options.interactive and not ask('Undelete %s%s?', 'directory' if os.path.isdir(filename) else '', filename):
        return False
    if os.path.exists(undeleted_name):
        if not options.force:
            if not ask('Undeleted %s exists.  Remove it?', undeleted_name):
                return False
        if os.path.isdir(undeleted_name) and \
                not os.path.islink(undeleted_name) and \
                not libdelete.is_mountpoint(undeleted_name):
            shutil.rmtree(undleted_name)
        else:
            os.unlink(undeleted_name)
    if options.noop:
        print >>sys.stderr, "{0}: {1} would be undeleted".format(whoami, filename)
        return True
    os.rename(filename, undeleted_name)
    print >>sys.stderr, "{0}: {1} undeleted".format(whoami, filename)
    return True

def undelete(filename, options):
    r_undel = None
    if options.dirsonly:
        r_undel = False
    r_del = None
    if options.recursive:
        r_del = True
    if options.dirsonly:
        r_del = False
    deleted_files = libdelete.find_deleted_files(
        filename,
        recurse_undeleted_subdirs=r_undel,
        recurse_deleted_subdirs=r_del,
        follow_links=True, follow_mounts=True)
    if len(deleted_files) == 0:
        perror("{0}: No such file or directory".format(filename), _maybe=options.report_errors)
        return False
    deleted_files.sort(reverse=True, key=lambda x: x.count(os.path.sep))
    for f in deleted_files:
        actually_undelete(f, options)
    return True


def get_filenames_from_stdin(options):
    errors = 0
    if options.verbose:
        print "Enter the files to be undeleted, one file per line."
        print "Hit <RETURN> on a line by itself to exit.\n"
    while True:
        try:
            filename = raw_input("{0}: ".format(whoami))
        except (EOFError, KeyboardInterrupt):
            sys.exit(errors)
        if len(filename) == 0:
            sys.exit(errors)
        try:
            undelete(filename, options)
        except libdelete.DeleteError as e:
            perror(e.message, _maybe=options.report_errors)
            errors = 1
    # Control should never get here, but just for good measure
    sys.exit(errors)

def main():
    rv = 0
    parser = optparse.OptionParser(usage="%prog [options] filename ...")
    parser.add_option(
        "-r", dest="recursive", action="store_true", default=False,
        help="Recursively delete non-empty directories")
    parser.add_option(
        "-f", dest="force", action="store_true", default=False,
        help="Do not ask questions or report errors about nonexistent files")
    parser.add_option(
        "-i", dest="interactive", action="store_true", default=False,
        help="Prompt for confirmation before deleting each file/directory")
    parser.add_option(
        "-n", dest="noop", action="store_true", default=False,
        help="Don't actually delete, just print what would be deleted")
    parser.add_option(
        "-v", dest="verbose", action="store_true", default=False,
        help="Print each filename as it is deleted")
    parser.add_option(
        "-R", dest="dirsonly", action="store_true", default=False,
        help="directories only (no recursion)")
    parser.add_option(
        "--debug", action="callback", type='string', help="Enable debugging (logger target or 'all')",
        callback=debug_callback, metavar='target')
    (options, args) = parser.parse_args()
    if options.recursive and options.dirsonly:
        parser.error("-r and -R are mutually exclusive")
    options.report_errors = not options.force
    if len(args) < 1:
        get_filenames_from_stdin(options)
    for filename in args:
        try:
            undelete(filename, options)
        except libdelete.DeleteError as e:
            perror(e.message, _maybe=options.report_errors)
            rv = 1
    return rv

if __name__ == "__main__":
    logging.basicConfig(level=logging.WARNING)
    sys.exit(main())
