#!/usr/bin/python3

'''Rearrange a series of lines on stdin into a random order'''

import os
import sys
import errno
import random
import collections

sys.path.append('/usr/local/lib')

import python2x3

def usage(retval):
    '''Give a usage message'''
    sys.stderr.write('Usage: %s [-0] [-h|--help]\n' % sys.argv[0])
    sys.stderr.write('-0 gives null termination instead of newline termination\n')
    sys.stderr.write('--preserve-directories says to rearrange directories, and the files within them,\n')
    sys.stderr.write('   but keep things in the same directory next to each other\n')
    sys.stderr.write('--skip-size says the file size will be at the beginning of the line, separated from the filename by a blank\n')
    sys.exit(retval)

def newlines():
    '''Generate lines of text, minus their newline terminator'''
    for line in sys.stdin:
        if line[-1:] == '\n':
            yield line[:-1]
        else:
            yield line

def my_range(maximum):
    '''Generate the values from 0..maximum-1, for consistent semantics between python 2.x and python 3.x'''
    value = 0
    while value < maximum:
        yield value
        value += 1

def shuffle(list_):
    '''rearrange elements of list_ into random order'''
    randobj = random.Random()

    temp_list = list_[:]
    num_elements = len(temp_list)

    for element_no in my_range(num_elements):
        random_element_no = int(randobj.random() * num_elements)
        temp_list[element_no], temp_list[random_element_no] = temp_list[random_element_no], temp_list[element_no]

    return temp_list

def directory_shuffle(list_, skip_size=False):
    '''rearrange elements of list_ into random order, but keep elements of a single directory adjacent'''

    #randobj = random.Random()

    dict_ = collections.defaultdict(list)

    for element in list_:
        if skip_size:
            assert ' ' in element
            parts = element.partition(' ')
            assert parts[1] == ' '
            assert len(parts) == 3
            dirname = os.path.dirname(parts[2])
            #basename = os.path.basename(parts[2])
        else:
            line = element
            dirname = os.path.dirname(element)
            #basename = os.path.basename(element)
        dict_[dirname].append(element)

    dirnames = dict_.keys()
    # Keys come out of the dictionary in an arbitrary order, but it might be too consistent for some purposes - so we shuffle them
    shuffle(dirnames)

    result = []

    for dirname in dirnames:
        lines = dict_[dirname]
        shuffle(lines)
        for line in lines:
            result.append(line)

    return result

def get_input(generator, verbose, every_n):
    '''Get the lines of input we need to rearrange'''
    list_ = []

    for element_no, line in enumerate(generator()):
        if verbose and element_no % every_n == 0 and element_no != 0:
            sys.stderr.write('read %d lines\n' % element_no)
        list_.append(line)

    return list_

def main():
    '''Main function'''
    use_readline0 = False
    verbose = False
    every_n = 1000
    preserve_directories = False
    skip_size = False
    warnings = True

    while sys.argv[1:]:
        if sys.argv[1] == '-0':
            use_readline0 = True
        elif sys.argv[1] == '--preserve-directories':
            preserve_directories = True
        elif sys.argv[1] == '--skip-size':
            skip_size = True
        elif sys.argv[1] == '-v':
            verbose = True
        elif sys.argv[1] == '--no-warnings':
            warnings = False
        elif sys.argv[1] in [ '-h', '--help' ]:
            usage(0)
        else:
            sys.stderr.write('%s: Illegal option: %s\n' % (sys.argv[0], sys.argv[1]))
            usage(1)
        del sys.argv[1]

    if use_readline0:
        import readline0

    if use_readline0:
        generator = readline0.readline0
        terminator = python2x3.string_to_binary('\0')
    else:
        #generator = sys.stdin.readline
        generator = newlines
        terminator = python2x3.string_to_binary('\n')

    list_ = get_input(generator, verbose, every_n)

    if warnings:
        if preserve_directories and not skip_size:
            all_have_blank = True
            for element in list_:
                if ' ' in element:
                    continue
                else:
                    all_have_blank = False
                    break
            if all_have_blank:
                sys.stderr.write('Warning: All lines have a blank in them, but --skip-size not given.  --no-warnings to suppress this message\n')

    if verbose:
        sys.stderr.write('Read a total of %d lines, about to start shuffling\n' % len(list_))

    if preserve_directories:
        shuffled_list = directory_shuffle(list_, skip_size)
    else:
        shuffled_list = shuffle(list_)

    if verbose:
        sys.stderr.write('Done shuffling\n')

    try:
        for element in shuffled_list:
            os.write(1, python2x3.string_to_binary(element) + terminator)
    except OSError:
        _unused, extra, _unused = sys.exc_info()
        if extra.errno == errno.EPIPE:
            sys.exit(0)
        else:
            raise

main()