#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import unicode_literals, print_function

import os, sys, re, locale
from path import Path

# class Asc(object):
#   def __init__(self, obj, *args): self.obj = obj
#   def __lt__(self, other): return self.obj <  other.obj
#   def __gt__(self, other): return self.obj >  other.obj
#   def __eq__(self, other): return self.obj == other.obj
#   def __le__(self, other): return self.obj <= other.obj
#   def __ge__(self, other): return self.obj >= other.obj
#   def __ne__(self, other): return self.obj != other.obj
def Asc(object):
  # Just return the argument since normal comparison is the default.
  return object

class Dsc(object):
  """ Used to reverse the sort order for its argument. """
  def __init__(self, obj, *args): self.obj = obj
  def __lt__(self, other): return self.obj >  other.obj
  def __gt__(self, other): return self.obj <  other.obj
  def __eq__(self, other): return self.obj == other.obj
  def __le__(self, other): return self.obj >= other.obj
  def __ge__(self, other): return self.obj <= other.obj
  def __ne__(self, other): return self.obj != other.obj


def main():
  # atime = last read/write or attribute change
  # mtime = contents changed
  # ctime = contents or attributes changed
  attrs = "name dir path mode ino dev nlink uid gid size atime mtime ctime".\
          split(" ")
  attr2index = dict(zip(attrs, xrange(len(attrs)))) # {'name':0, ...}
  rx_attrs = re.compile(r'[+-]('+"|".join(attrs)+")")
  rx_limit = re.compile(r'[+-]\d+')

  # Parse arguments:
  options, folders = [], []
  limit = None

  for arg in sys.argv[1:]:
    if arg in ('+time', '-time'): # Synonym for ctime.
      arg = arg[:1] + 'ctime'
    if rx_attrs.match(arg):
      options += [((Dsc, Asc)[arg[:1] == '+'], attr2index[arg[1:]])] # E.g.: "+size" --> (Asc, 9)
    elif rx_limit.match(arg):
      limit = int(arg)
    else:
      folders += [Path(arg)]

  if not len(folders):
    folders = [Path('.')]

  # Check existence.
  for folder in folders:
    if not folder.exists:
      return print("Error: folder ‘{}’ does not exist.".format(folder))
    elif folder.isfile:
      return print("Error: path ‘{}’ is not a folder.".format(folder))

  # Walk the folders and get all the files and their attributes:
  paths = []

  def get_attrs(path):
    #       name,      dir,         path,    file_attributes
    return (path.name, path.folder, path,) + tuple(os.stat(path))

  for folder in folders:
    for (root, dirs, files) in folder.walk():
      paths.extend(get_attrs(Path(root, name)) for name in (dirs + files) if Path(root, name).exists)


  # Sort the list by the requested attributes.
  if len(options):
    # TODO: use locale.strxfrm?
    def lower_cmp(func):
      return lambda x: func(unicode.lower(x))
    # o[1] < 3 because the first three array items are path strings (see get_attrs).
    options = [(lower_cmp(o[0]), o[1]) if o[1] < 3 else o for o in options]

    def sorty_by_multiple(row):
      """ Returns a list of Dsc or Asc objects wrapping the items in 'row'. """
      return [comparator(row[attr_index]) for (comparator, attr_index) in options]

    comparator, attr_index = options[0]

    def sorty_by_single(row):
      """ Returns a Dsc or Asc object wrapping one item in 'row'. """
      return comparator(row[attr_index])

    paths.sort(key=(sorty_by_single, sorty_by_multiple)[len(options) != 1])

  # Limit results
  if limit is not None:
    paths = paths[limit:] if limit < 0 else paths[:limit]

  for path in paths:
    name = path[2]
    if path[2].isdir:
      name += '/'
    print(name.encode('utf-8'))


if __name__ == '__main__':
  main()
