#!/usr/bin/env python

from fs.errors import FSError
from fs.opener import opener
from fs.path import pathsplit, abspath, isdotfile, iswildcard
from fs.commands.runner import Command
from collections import defaultdict
import sys

class FSls(Command):

    usage = """fsls [OPTIONS]... [PATH]
List contents of [PATH]"""


    def get_optparse(self):
        optparse = super(FSls, self).get_optparse()
        optparse.add_option('-u', '--full', dest='fullpath', action="store_true", default=False,
                            help="output full path", metavar="FULL")
        optparse.add_option('-s', '--syspath', dest='syspath', action="store_true", default=False,
                            help="output system path (if one exists)", metavar="SYSPATH")
        optparse.add_option('-r', '--url', dest='url', action="store_true", default=False,
                            help="output URL in place of path (if one exists)", metavar="URL")
        optparse.add_option('-d', '--dirsonly', dest='dirsonly', action="store_true", default=False,
                            help="list directories only", metavar="DIRSONLY")
        optparse.add_option('-f', '--filesonly', dest='filesonly', action="store_true", default=False,
                            help="list files only", metavar="FILESONLY")
        optparse.add_option('-l', '--long', dest='long', action="store_true", default=False,
                            help="use a long listing format", metavar="LONG")
        optparse.add_option('-a', '--all', dest='all', action='store_true', default=False,
                            help="do not hide dot files")

        return optparse


    def do_run(self, options, args):
        output = self.output

        if not args:
            args = ['.']

        dir_paths = []
        file_paths = []
        fs_used = set()
        for fs_url in args:
            fs, path = self.open_fs(fs_url)
            fs_used.add(fs)
            path = path or '.'
            wildcard = None

            if iswildcard(path):
                path, wildcard = pathsplit(path)

            if path != '.' and fs.isfile(path):
                if not options.dirsonly:
                    file_paths.append(path)
            else:
                if not options.filesonly:
                    dir_paths += fs.listdir(path,
                                            wildcard=wildcard,
                                            full=options.fullpath or options.url,
                                            dirs_only=True)

                if not options.dirsonly:
                    file_paths += fs.listdir(path,
                                             wildcard=wildcard,
                                             full=options.fullpath or options.url,
                                             files_only=True)

        for fs in fs_used:
            try:
                fs.close()
            except FSError:
                pass

        if options.syspath:
            # Path without a syspath, just won't be displayed
            dir_paths = [_f for _f in [fs.getsyspath(path, allow_none=True) for path in dir_paths] if _f]
            file_paths = [_f for _f in [fs.getsyspath(path, allow_none=True) for path in file_paths] if _f]

        if options.url:
            # Path without a syspath, just won't be displayed
            dir_paths = [_f for _f in [fs.getpathurl(path, allow_none=True) for path in dir_paths] if _f]
            file_paths = [_f for _f in [fs.getpathurl(path, allow_none=True) for path in file_paths] if _f]

        dirs = frozenset(dir_paths)
        paths = sorted(dir_paths + file_paths, key=lambda p: p.lower())

        if not options.all:
            paths = [path for path in paths if not isdotfile(path)]

        if not paths:
            return

        def columnize(paths, num_columns):

            col_height = (len(paths) + num_columns - 1) / num_columns
            columns = [[] for _ in range(num_columns)]
            col_no = 0
            col_pos = 0
            for path in paths:
                columns[col_no].append(path)
                col_pos += 1
                if col_pos >= col_height:
                    col_no += 1
                    col_pos = 0

            padded_columns = []

            wrap_filename = self.wrap_filename
            wrap_dirname = self.wrap_dirname

            def wrap(path):
                if path in dirs:
                    return wrap_dirname(path.ljust(max_width))
                else:
                    return wrap_filename(path.ljust(max_width))

            for column in columns:
                if column:
                    max_width = max([len(path) for path in column])
                else:
                    max_width = 1
                max_width = min(max_width, terminal_width)
                padded_columns.append([wrap(path) for path in column])

            return padded_columns

        def condense_columns(columns):
            max_column_height = max([len(col) for col in columns])
            lines = [[] for _ in range(max_column_height)]
            for column in columns:
                for line, path in zip(lines, column):
                    line.append(path)
            return '\n'.join('  '.join(line) for line in lines)

        if options.long:
            for path in paths:
                if path in dirs:
                    output((self.wrap_dirname(path), '\n'))
                else:
                    output((self.wrap_filename(path), '\n'))

        else:
            terminal_width = self.terminal_width
            path_widths = [len(path) for path in paths]
            smallest_paths = min(path_widths)
            num_paths = len(paths)

            num_cols = min(terminal_width // (smallest_paths + 2), num_paths)
            while num_cols:
                col_height = (num_paths + num_cols - 1) // num_cols
                line_width = 0
                for col_no in range(num_cols):
                    try:
                        col_width = max(path_widths[col_no * col_height: (col_no + 1) * col_height])
                    except ValueError:
                        continue
                    line_width += col_width
                    if line_width > terminal_width:
                        break
                    line_width += 2
                else:
                    if line_width - 1 <= terminal_width:
                        break
                num_cols -= 1
            num_cols = max(1, num_cols)
            columns = columnize(paths, num_cols)
            output((condense_columns(columns), '\n'))

def run():
    return FSls().run()

if __name__ == "__main__":
    sys.exit(run())
