#
# Copyright (c) 2013-2022 CERN
#
# Copyright (c) 2012-2013 Members of the EMI Collaboration
#    See http://www.eu-emi.eu/partners for details on the copyright
#    holders.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

#from __future__ import absolute_import # not available in python 2.4
from __future__ import division

import logging
import math
from datetime import datetime
import os
import sys
import stat

from gfal2_util import base
from gfal2_util.utils import file_mode_str


def full_iso(date):
    dt = datetime.fromtimestamp(date)
    if sys.version_info >= (2, 6):
        return dt.strftime("%Y-%m-%d %H:%M:%S.%f +0000")
    else:
        # python 2.4 and 2.5 doesn't support %f in strftime
        return dt.strftime("%Y-%m-%d %H:%M:%S.000000 +0000")


def long_iso(date):
    dt = datetime.fromtimestamp(date)
    return dt.strftime("%Y-%m-%d %H:%M")


def time_iso(date):
    dt = datetime.fromtimestamp(date)
    dt_now = datetime.now()

    diff_months = (dt_now - dt).days / 30  # approximate...

    if diff_months < 6:
        return dt.strftime("%m-%d %H:%M")
    else:
        return dt.strftime("%Y-%m-%d")


def time_locale(date):
    dt = datetime.fromtimestamp(date)
    dt_now = datetime.now()

    diff_months = (dt_now - dt).days / 30  # approximate...
    day = dt.strftime("%d").lstrip("0").rjust(2)

    if diff_months < 6:
        return dt.strftime("%b " + day + " %H:%M")
    else:
        return dt.strftime("%b " + day + "  %Y")


time_formats = {
    'full-iso': full_iso,
    'long-iso': long_iso,
    'iso': time_iso,
    'locale': time_locale
}

color_dict = dict()
color_env = os.environ.get('LS_COLORS', None)
if color_env:
    for entry in [entry for entry in color_env.split(':') if '=' in entry]:
        try:
            typ, color = entry.split('=')
            color_dict[typ] = color
        except Exception:
            e = sys.exc_info()[1]
            sys.stderr.write("unparsable value for LS_COLORS environment variable: %s\n" % entry)


class CommandLs(base.CommandBase):
    @base.arg('-a', '--all', action="store_true", help="display hidden files")
    @base.arg('-l', '--long', action="store_true", help='long listing format')
    @base.arg('-d', '--directory', action="store_true",
              help='list directory entries instead of contents')
    @base.arg('-H', '--human-readable', action="store_true",
              help='with -l, prints size in human readable format (e.g., 1K 234M 2G')
    @base.arg('--xattr', type=str, action='append', default=[],
              help="query additional attributes. Can be specified multiple times. Only works for --long output")
    @base.arg('--time-style', type=str, default='locale', choices=list(time_formats.keys()),
              help="time style")
    @base.arg('--full-time', action="store_true", help="same as --time-style=full-iso")
    @base.arg('--color', type=str, choices=['always', 'never', 'auto'], default='auto',
              help='print colored entries with -l')
    @base.arg('file', type=base.surl, help="file's uri")
    def execute_ls(self):
        """List directory's contents"""
        if self.params.full_time:
            self.params.time_style = 'long-iso'

        st = self.context.stat(self.params.file)

        if stat.S_ISDIR(st.st_mode) and not self.params.directory:
            directory = self.context.opendir(self.params.file)
            st = None
            while True:
                if self.params.long:
                    (dirent, st) = directory.readpp()
                else:
                    dirent = directory.read()
                if dirent is None or dirent.d_name is None or len(dirent.d_name) == 0:
                    break

                if not self.params.all and dirent.d_name[0] == '.':
                    continue

                extra = list()
                if self.params.long:
                    for xattr in self.params.xattr:
                        surl = os.path.join(self.params.file, dirent.d_name)
                        extra.append(self.context.getxattr(surl, xattr))
                self._print_ls_entry(dirent.d_name, st, extra)
        else:
            extra = list()
            if self.params.long:
                for xattr in self.params.xattr:
                    extra.append(self.context.getxattr(self.params.file, xattr))
            self._print_ls_entry(self.params.file, st, extra)

        return 0

    def _print_ls_entry(self, name, stat, extra=None):
        space = {
            'st_mode': 5, 'st_nlink': 3, 'st_gid': 5, 'st_uid': 5,
            'st_mtime': 11, 'size': 9, 'size_human': 4
        }

        #if long, print some stuff from stat. Try to align as best as possible without buffering
        if self.params.long:
            size = stat.st_size
            size_sp = space['size']

            if self.params.human_readable:
                size = self._size_to_human(size)
                size_sp = space['size_human']

            date = time_formats[self.params.time_style](stat.st_mtime)

            extra_str = ''
            if extra:
                extra_str = '\t'.join(extra)

            sys.stdout.write(
                "%s %s %s %s %s %s %s\t%s\n" % (
                    file_mode_str(stat.st_mode),
                    str(stat.st_nlink).rjust(space['st_nlink']),
                    str(stat.st_uid).ljust(space['st_uid']),
                    str(stat.st_gid).ljust(space['st_gid']),
                    str(size).rjust(size_sp),
                    str(date).ljust(space['st_mtime']),
                    self.color(name, stat.st_mode),
                    extra_str
                )
            )
        else:
            sys.stdout.write("%s\n" % self.color(name, None))

    @staticmethod
    def _size_to_human(size):
        degree_symbols = ['', 'K', 'M', 'G', 'T', 'P']
        degree = 0
        while float(size) >= 1024.0 and degree < len(degree_symbols)-1:
            size = float(size) / 1024.0
            degree += 1

        #round up the result
        if size < 10.0:
            return "%0.1f%s" % (math.ceil(size*10.0)/10.0, degree_symbols[degree])
        else:
            return "%0.0f%s" % (math.ceil(size), degree_symbols[degree])

    def color(self, name, mode):
        apply_color = False
        if self.params.color == 'always':
            apply_color = True
        elif self.params.color == 'auto':
            apply_color = sys.stdout.isatty()

        if not apply_color:
            return name

        color = '037'
        if mode is None:
            color = color_dict.get('no', color)
        elif stat.S_ISDIR(mode):
            color = color_dict.get('di', color)
        elif stat.S_ISLNK(mode):
            color = color_dict.get('ln', color)
        elif mode & (stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH):
            color = color_dict.get('ex', color)
        return '\033[%sm%s\033[0m' % (color, name)