#!/usr/bin/env python3
#
# Copyright (C) 2019 IBM Corporation.
#
# Authors:
# Frederico Araujo <frederico.araujo@ibm.com>
# Teryl Taylor <terylt@ibm.com>
#
# 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.
#
import logging, sys, os, argparse, tempfile

sys.path.append('.')
from sysflow import __version__
from sysflow.reader import FlattenedSFReader
from sysflow.formatter import SFFormatter
from minio import Minio

"""
.. module:: sysprint
   :synopsis: This module implements a command-line tool for printing and converting SysFlow from disk and S3 object stores to formats other than avro including JSON, CSV, and tabular pretty print
.. moduleauthor:: Frederico Araujo, Teryl Taylor
"""


def files(path):
    """list files in dir path"""
    for file in os.listdir(path):
        if os.path.isfile(os.path.join(path, file)):
            yield os.path.join(path, file)


def run(args):
    """execute sysprint"""
    if args.input == 's3':
        s3 = Minio(
            '%s:%s' % (args.s3endpoint, args.s3port),
            access_key=args.s3accesskey,
            secret_key=args.s3secretkey,
            secure=args.secure,
        )
        for path in args.paths:
            if s3.bucket_exists(path):
                objs = s3.list_objects(path)
                with tempfile.TemporaryDirectory() as tmpdir:
                    for obj in objs:
                        s3.fget_object(
                            obj.bucket_name,
                            obj.object_name,
                            '/{0}/{1}/{2}'.format(tmpdir, obj.bucket_name, obj.object_name),
                        )
                        sysprint('/{0}/{1}/{2}'.format(tmpdir, obj.bucket_name, obj.object_name), args)
            else:
                logging.warn('Bucket {0} does not exist.'.format(path))
    elif args.input == 'local':
        for path in args.paths:
            if os.path.isfile(path):
                sysprint(path, args)
            elif os.path.isdir(path):
                traces = [f for f in files(path)]
                traces.sort(key=lambda f: int(''.join(filter(str.isdigit, f))))
                for t in traces:
                    sysprint(t, args)
            else:
                logging.warn('Existent file or dir path expected.')


def sysprint(trace, args):
    """print a sysflow file in human-readable format"""
    reader = FlattenedSFReader(trace, False)
    formatter = SFFormatter(reader)
    if args.k8s:
        formatter.enablePodFields()
    elif args.k8sevents:
        formatter.enableK8sEventFields()
    fields = args.fields.split(',') if args.fields else None
    if args.allfields:
        fields = None  # override specified fields
        formatter.enableAllFields()
    if args.output == 'json':
        if args.file is not None:
            formatter.toJsonFile(args.file, fields=fields, expr=args.filter)
        else:
            formatter.toJsonStdOut(fields=fields, expr=args.filter)
    elif args.output == 'flatjson':
        if args.file is not None:
            formatter.toJsonFile(args.file, fields=fields, expr=args.filter, flat=True)
        else:
            formatter.toJsonStdOut(fields=fields, expr=args.filter, flat=True)
    elif args.output == 'csv' and args.file is not None:
        formatter.toCsvFile(args.file, fields=fields, expr=args.filter)
    elif args.output == 'str':
        formatter.toStdOut(fields=fields, expr=args.filter)
    else:
        raise argparse.ArgumentTypeError('unknown output type.')


def str2bool(v):
    """argparse facility for boolean type conversions"""
    if isinstance(v, bool):
        return v
    if v.lower() in ('yes', 'true', 't', 'y', '1'):
        return True
    elif v.lower() in ('no', 'false', 'f', 'n', '0'):
        return False
    else:
        raise argparse.ArgumentTypeError('Boolean value expected.')


class _ListAction(argparse.Action):
    def __init__(self, option_strings, dest='==SUPPRESS==', default='==SUPPRESS==', help=None):
        super(_ListAction, self).__init__(option_strings=option_strings, dest=dest, default=default, nargs=0, help=help)

    def __call__(self, parser, values, namespace, option_strings=None):
        formatter = SFFormatter(None)
        print('-' * 30)
        print('List of attributes:')
        print('-' * 30)
        for k, v in formatter.getFields():
            print('{:<40} {:<40}'.format(k, v))
        print('-' * 30)
        parser.exit()


class _VersionAction(argparse.Action):
    def __init__(self, option_strings, dest=argparse.SUPPRESS, default=argparse.SUPPRESS, help=None):
        super(_VersionAction, self).__init__(
            option_strings=option_strings, dest=dest, default=default, nargs=0, help=help
        )

    def __call__(self, parser, namespace, values, option_string=None):
        print(__version__)
        parser.exit()


if __name__ == '__main__':

    # set command line args
    parser = argparse.ArgumentParser(
        description='sysprint: a human-readable printer and format converter for Sysflow captures.'
    )
    parser.register('action', 'list_fields', _ListAction)
    parser.add_argument(
        'paths', metavar='path', nargs='+', help='list of paths or bucket names from where to read trace files'
    )
    parser.add_argument('-i', '--input', help='input type', choices=['local', 's3'], default='local')
    parser.add_argument(
        '-o', '--output', help='output format', choices=['str', 'flatjson', 'json', 'csv'], default='str'
    )
    parser.add_argument('-w', '--file', help='output file path', default=None)
    parser.add_argument('-c', '--fields', help='comma-separated list of sysflow fields to be printed', default=None)
    parser.add_argument('-f', '--filter', help='filter expression', default=None)
    parser.add_argument('-l', '--list', help='list available record attributes', action='list_fields')
    group = parser.add_mutually_exclusive_group(required=False)
    group.add_argument('-k', '--k8s', help='add pod related fields to output', action='store_true')
    group.add_argument('-K', '--k8sevents', help='use k8s event fields in output', action='store_true')
    parser.add_argument('-A', '--allfields', help='add all available fields to output', action='store_true')
    parser.add_argument('-e', '--s3endpoint', help='s3 server address from where to read sysflows', default=None)
    parser.add_argument('-p', '--s3port', help='s3 server port', default=443)
    parser.add_argument('-a', '--s3accesskey', help='s3 access key', default=None)
    parser.add_argument('-s', '--s3secretkey', help='s3 secret key', default=None)
    parser.add_argument(
        '--secure', help='indicates if SSL connection', type=str2bool, nargs='?', const=True, default=True
    )
    parser.add_argument('-v', '--version', help='print version information', action=_VersionAction)

    # parse args and configuration
    args = parser.parse_args()

    if not os.environ.get('TZ'):
        os.environ['TZ'] = 'UTC'

    # setup logging
    logging.basicConfig(level=logging.INFO, format='%(message)s')

    # input validation
    if args.output == 'csv' and args.file is None:
        raise argparse.ArgumentTypeError('Output file path is required for CSV output.')

    # run sysprint
    try:
        run(args)
    except (KeyboardInterrupt, SystemExit):
        pass
    except Exception as e:
        logging.error('Error while executing sysprint. (' + str(e) + ')')
    else:
        sys.exit(0)
