#
# 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 gfal2
import sys
import stat
from datetime import datetime

from gfal2_util import base
from gfal2_util.utils import file_type_str, file_mode_str


class GfalCommands(base.CommandBase):
    @base.arg('-m', '--mode', action='store', type=int, default=755, help="display hidden files")
    @base.arg('-p', '--parents', action='store_true', help="no error if existing, make parent directories as needed")
    @base.arg('directory', action='store', nargs='+', type=base.surl, help="Directory's uri")
    def execute_mkdir(self):
        """
        Makes directories. By default, it sets file mode 0755.
        """
        if self.params.mode:
            try:
                mode = int(str(self.params.mode), 8)
            except ValueError:
                mode = int('755', 8) # python < 2.6 doesn't support 0o755
        else:
            mode = int('755', 8) # python < 2.6 doesn't support 0o755
        for d in self.params.directory:
            if self.params.parents:
                self.context.mkdir_rec(d, mode)
            else:
                self.context.mkdir(d, mode)

    @base.arg('file', action='store', type=base.surl, help="uri of the file to be written")
    def execute_save(self):
        """
        Reads from stdin and writes to a file. If the file exists, it will be overwritten
        """
        f = self.context.open(self.params.file, 'w')

        while True:
            b = sys.stdin.read(65000)
            if b:
                f.write(b)
            else:
                break

    @base.arg('-b', '--bytes', action='store_true', help="handle file contents as bytes (only in Python3)")
    @base.arg('file', action='store', nargs='+', type=base.surl, help="uri of the file to be displayed")
    def execute_cat(self):
        """
        Sends to stdout the contents of files
        """

        for fname in self.params.file:
            f = self.context.open(fname, 'r')

            while True:
                if self.params.bytes and sys.version_info >= (3, 0):
                    b = f.read_bytes(65000)
                    sys.stdout.buffer.write(b)
                else:
                    b = f.read(65000)
                    sys.stdout.write(b)
                if not b:
                    break

    @base.arg('file', action='store', type=base.surl, help="file uri")
    @base.arg('attribute', nargs='?', type=str, help="attribute to retrieve or set. To set, use key=value")
    def execute_xattr(self):
        """
        Gets or set the extended attributes of files and directories
        """
        if self.params.attribute is not None:
            if '=' in self.params.attribute:
                i = self.params.attribute.find("=")
                attr = self.params.attribute[:i]
                v = self.params.attribute[(i+1):]

                if len(attr) > 0 and len(v) > 0:
                    self.context.setxattr(self.params.file, attr, v, 0)
            else:
                v = self.context.getxattr(self.params.file, self.params.attribute)
                sys.stdout.write(v + '\n')
        else:
            l = self.context.listxattr(self.params.file)

            for attr in l:
                try:
                    v = self.context.getxattr(self.params.file, attr)
                    sys.stdout.write(attr + ' = ' + v + '\n')
                except gfal2.GError:
                    e = sys.exc_info()[1]
                    sys.stdout.write(attr + ' FAILED: ' + str(e) + '\n')

    @base.arg('file', action='store', type=base.surl,
              help="file uri to use for checksum calculation")
    @base.arg('checksum_type', action='store', type=str,
              help="checksum algorithm to use. For example: ADLER32, CRC32, MD5")
    def execute_sum(self):
        """
        Calculates the checksum of a file
        """
        checksum = self.context.checksum(self.params.file, self.params.checksum_type)
        sys.stdout.write(self.params.file + ' ' + checksum + '\n')

    @base.arg('file', action='store', type=base.surl, help="uri of the file to be stat")
    def execute_stat(self):
        """
        Stats a file
        """
        st = self.context.stat(self.params.file)
        print("  File: '%s'" % self.params.file)
        print("  Size: %d\t%s" % (st.st_size, file_type_str(stat.S_IFMT(st.st_mode))))
        print("Access: (%04o/%s)\tUid: %d\tGid: %d\t" % (stat.S_IMODE(st.st_mode), file_mode_str(st.st_mode), st.st_uid, st.st_gid))
        if sys.version_info >= (2, 6):
            print("Access: %s" % datetime.fromtimestamp(st.st_atime).strftime("%Y-%m-%d %H:%M:%S.%f"))
            print("Modify: %s" % datetime.fromtimestamp(st.st_mtime).strftime("%Y-%m-%d %H:%M:%S.%f"))
            print("Change: %s" % datetime.fromtimestamp(st.st_ctime).strftime("%Y-%m-%d %H:%M:%S.%f"))
        else:
            # python 2.4 and 2.5 doesn't support %f in strftime
            print("Access: %s" % datetime.fromtimestamp(st.st_atime).strftime("%Y-%m-%d %H:%M:%S.000000"))
            print("Modify: %s" % datetime.fromtimestamp(st.st_mtime).strftime("%Y-%m-%d %H:%M:%S.000000"))
            print("Change: %s" % datetime.fromtimestamp(st.st_ctime).strftime("%Y-%m-%d %H:%M:%S.000000"))

    @base.arg('source', action='store', type=base.surl, help="original file name")
    @base.arg('destination', action='store', type=base.surl, help="new file name")
    def execute_rename(self):
        """
        Renames files or directories
        """
        self.context.rename(self.params.source, self.params.destination)

    @base.arg('mode', action='store', type=str, help="new mode, in octal")
    @base.arg('file', action='store', type=base.surl, help="uri of the file to change permissions")
    def execute_chmod(self):
         """
         Change the permissions of a file
         """
         try:
            mode = int(self.params.mode, base=8)
         except ValueError:
             self.parser.error('Mode must be an octal number (i.e. 0755)')
             return
         self.context.chmod(self.params.file, mode)

    @base.arg('--issuer', action='store', type=str, help="token issuer URL")
    @base.arg('--validity', action='store', type=int, default=60, help="token validity in minutes")
    @base.arg('-w', '--write', dest='write_access', action='store_true', help="flag to request write access token")
    @base.arg('path', action='store', type=base.surl, help="URI to request token for")
    @base.arg('activities', action='store', nargs='*', type=str, help="activities for macaroon request")
    def execute_token(self):
        """
        Retrieve a SE-issued token
        """
        if self.params.validity < 0:
            sys.stderr.write("Validity must be a number >= 0\n")
            return 1

        if self.params.verbose:
            if len(self.params.activities) > 0:
                print("Will use user-provided activities")
            else:
                print("Will use default activities for {} access"
                      .format(['read', 'write'][self.params.write_access]))

        issuer = self.params.issuer if self.params.issuer is not None else ''
        if len(self.params.activities) > 0:
            token = self.context.token_retrieve(self.params.path, issuer, self.params.validity,
                                                self.params.activities)
        else:
            token = self.context.token_retrieve(self.params.path, issuer, self.params.validity,
                                                self.params.write_access)
        sys.stdout.write(token + '\n')
