#!/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 = [u'.']
            
        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 = filter(None, [fs.getsyspath(path, allow_none=True) for path in dir_paths])
            file_paths = filter(None, [fs.getsyspath(path, allow_none=True) for path in file_paths])
                          
        if options.url:
            # Path without a syspath, just won't be displayed
            dir_paths = filter(None, [fs.getpathurl(path, allow_none=True) for path in dir_paths])
            file_paths = filter(None, [fs.getpathurl(path, allow_none=True) for path in file_paths])
                        
        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 xrange(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 xrange(max_column_height)]
            for column in columns:
                for line, path in zip(lines, column):
                    line.append(path)                                
            return '\n'.join(u'  '.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 xrange(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())
        