#!/usr/bin/python3

# pylint: disable=superfluous-parens
# superfluous-parens: Parens are good for clarity and portability

"""Find the most recent files."""

# Someday we could use a treap or similar to get the n most recent, but for now, we always list all files,
# because timsort is pretty blazing, and it's rare to have so many files that it'll matter.

# pylint: disable=wrong-import-position

import os
import sys
import glob
import time

sys.path.insert(0, '/usr/local/lib')

import python2x3  # noqa: ignore=E402
import readline0  # noqa: ignore=E402


def make_used(*var):
    """Pretend var is used, to keep pyflakes/pylint happy."""
    assert True or var


def usage(retval):
    """Output a usage message."""
    if retval == 0:
        file_ = sys.stdout
    else:
        file_ = sys.stderr
    file_.write('Usage: {}\n'.format(sys.argv[0]))
    file_.write('    [--glob | --here | --stdin]\n')
    file_.write('    [--heap | --sort]\n')
    file_.write('    [--count n]\n')
    file_.write('    [--ctime | --atime]\n')
    file_.write('    --filenames-only\n')
    file_.write('\n')
    file_.write('In --here mode, os.walk() is used to traverse the CWD\n')
    file_.write('In --glob mode, glob.glob() is used to list the CWD nonrecursively\n')
    file_.write('In --stdin mode, readline0() is used to obtain null-terminated filenames from stdin\n')
    file_.write('\n')
    file_.write('In either case, the n most recent files are listed in order from most recent to least recent\n')
    file_.write('By default, n is 10\n')
    file_.write('\n')
    file_.write('With --heap a heap is used to get the n largest timestamps. This is the default.\n')
    file_.write('With --sort a big list and sort are used to get the n largest timestamps\n')
    file_.write('\n')
    file_.write('Note that --heap and --sort can produce different outputs sometimes, due to files with equal timestamps\n')
    file_.write('\n')
    file_.write('--ctime says to use inode-change-times instead of modification times\n')
    file_.write('--atime says to use access times instead of modification times\n')
    sys.exit(retval)


def find_recent(timestamp_attribute, means, files_iter, count):
    """Find the n most recent files."""
    if means == 'sort':
        list_ = []
        for filename in files_iter():
            stat = os.lstat(filename)
            timestamp = getattr(stat, timestamp_attribute)
            tuple_ = (-timestamp, filename)
            list_.append(tuple_)
        list_.sort()
        del list_[count:]
    elif means == 'heap':
        import heapq
        heap = []
        for filename in files_iter():
            try:
                stat = os.lstat(filename)
            except OSError:
                os.write(2, b'Failed to lstat %s\n' % filename)
                continue
            timestamp = getattr(stat, timestamp_attribute)
            tuple_ = (timestamp, filename)
            heapq.heappush(heap, tuple_)
            if heap[count:]:
                dummy = heapq.heappop(heap)
                make_used(dummy)
        list_ = [(-timestamp, filename) for timestamp, filename in heap]
        # list_ = heap
        list_.sort()
    else:
        sys.stderr.write('{}: Internal error: means not sort or heap\n'.format(sys.argv[0]))
        sys.exit(1)

    return list_


class Options(object):
    # pylint: disable=too-few-public-methods
    # too-few-public-methods: We're just a container
    """Hold our command line option variables."""

    def __init__(self):
        """Parse command line options, setting instance attributes as needed."""
        self.means = ''
        self.count = 10

        self.mode_temp = ''

        self.timestamp_attribute = 'st_mtime'

        self.filenames_only = False

        self.parse()

        if self.means == '':
            self.means = 'heap'

        if self.mode_temp == '':
            sys.stderr.write('{}: You must specify exactly one of --here and --stdin\n'.format(sys.argv[0]))
            sys.exit(1)
        else:
            self.mode = self.mode_temp

    def parse(self):
        # pylint: disable=too-many-branches,too-many-statements
        """Do the actual conversion."""
        while sys.argv[1:]:
            if sys.argv[1] == '--count':
                self.count = int(sys.argv[2])
                del sys.argv[1]
            elif sys.argv[1] == '--filenames-only':
                self.filenames_only = True
            elif sys.argv[1] == '--ctime':
                self.timestamp_attribute = 'st_ctime'
            elif sys.argv[1] == '--atime':
                self.timestamp_attribute = 'st_atime'
            elif sys.argv[1] == '--here':
                if self.mode_temp != '':
                    sys.stderr.write('{}: --here, --glob and --stdin are incompatible\n'.format(sys.argv[0]))
                    sys.exit(1)
                self.mode_temp = 'here'
            elif sys.argv[1] == '--glob':
                if self.mode_temp != '':
                    sys.stderr.write('{}: --here, --glob and --stdin are incompatible\n'.format(sys.argv[0]))
                    sys.exit(1)
                self.mode_temp = 'glob'
            elif sys.argv[1] == '--stdin':
                if self.mode_temp != '':
                    sys.stderr.write('{}: --here, --glob and --stdin are incompatible\n'.format(sys.argv[0]))
                    sys.exit(1)
                self.mode_temp = 'readline0'
            elif sys.argv[1] == '--sort':
                if self.means != '':
                    sys.stderr.write('{}: --sort and --heap are incompatible\n'.format(sys.argv[0]))
                    sys.exit(1)
                self.means = 'sort'
            elif sys.argv[1] == '--heap':
                if self.means != '':
                    sys.stderr.write('{}: --sort and --heap are incompatible\n'.format(sys.argv[0]))
                    sys.exit(1)
                self.means = 'heap'
            elif sys.argv[1] in ['-h', '--help']:
                usage(0)
            else:
                sys.stderr.write('{}: Unrecognized option: {}\n'.format(sys.argv[0], sys.argv[1]))
                usage(1)
            del sys.argv[1]

        if self.mode_temp == 'here':
            pass
        elif self.mode_temp == 'glob':
            pass
        elif self.mode_temp == 'readline0':
            pass
        else:
            sys.stderr.write('{}: Not --here, --glob or --stdin\n'.format(sys.argv[0]))
            usage(1)

        if self.timestamp_attribute not in ('st_mtime', 'st_ctime', 'st_atime'):
            sys.stderr.write('{}: Internal error: timestamp_attribute has strange value: {}\n'.format(
                sys.argv[0],
                self.timestamp_attribute,
                ))


def main():
    """Parse options, stat files, sort and report."""
    options = Options()

    def files_iter():
        """Generate a list of files using os.walk() or readline0."""
        if options.mode == 'here':
            for dirname, subdir_list, filenames in os.walk(b'.'):
                dummy = subdir_list
                make_used(dummy)
                for filename in filenames:
                    yield os.path.join(dirname, filename)
        elif options.mode == 'readline0':
            for filename in readline0.readline0(file_=0):
                yield filename.rstrip(b'\0')
        elif options.mode == 'glob':
            for filename in glob.glob('*'):
                yield filename
        else:
            sys.stderr.write('{}: You must specify --here or --stdin\n'.format(sys.argv[0]))
            sys.exit(1)

    list_ = find_recent(options.timestamp_attribute, options.means, files_iter, options.count)

    for timestamp, filename in list_:
        try:
            if not options.filenames_only:
                neg_time = -timestamp
                sys.stdout.write('{:.3f}'.format(neg_time))
                sys.stdout.write(' ')
                sys.stdout.write(time.ctime(neg_time))
                sys.stdout.write(' ')
                sys.stdout.flush()
            os.write(1, python2x3.string_to_binary(filename) + b'\n')
        except BrokenPipeError:
            pass


main()