#!/usr/bin/python3
#
# Licensed under the GNU General Public License Version 3
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
# Copyright 2013 Aron Parsons <aronparsons@gmail.com>
#

""" spacecmd - a command line interface to Spacewalk """
from spacecmd.i18n import _N

import errno
import gettext
import logging
import os
import re
import sys
try:
    from xmlrpc import client as xmlrpclib
except ImportError:
    import xmlrpclib
import codecs
import locale
import argparse
try: # python 3
    from configparser import SafeConfigParser
except ImportError: # python 2
    from ConfigParser import SafeConfigParser
import socket

translation = gettext.translation('spacecmd', fallback=True)
try:
    _ = translation.ugettext
except AttributeError:
    _ = translation.gettext

_INTRO = _('''Welcome to spacecmd, a command-line interface to Spacewalk.

Type: 'help' for a list of commands
      'help <cmd>' for command-specific help
      'quit' to quit
''')

_SYSTEM_CONF_FILE = '/etc/spacecmd.conf'

def get_localhost_fqdn():
    """
    Get FQDN of the current machine.

    :return:
    """
    fqdn = None
    try:
        for family, socktype, proto, canonname, sockaddr in socket.getaddrinfo(
                socket.gethostname(), 0, 0, 0, 0, socket.AI_CANONNAME):
            if canonname:
                fqdn = canonname
                break
    except socket.gaierror as exc:
        logging.debug("Error while getting FQDN over the network: %s", str(exc))
    except Exception as exc:
        logging.error(_N("Unhandled exception occurred while getting FQDN:"), str(exc))

    return fqdn or socket.getfqdn()

def _redirect_stdout_to_devnull():
    # Python flushes standard streams on exit; redirect remaining output
    # to devnull to avoid another BrokenPipeError at shutdown
    devnull = os.open(os.devnull, os.O_WRONLY)
    os.dup2(devnull, sys.stdout.fileno())


if __name__ == '__main__':
    # disable no-member error message
    # pylint: disable=E1101

    try:
        if not sys.stdout.isatty():
            try: # Python 3
                sys.stdout = codecs.getwriter(locale.getpreferredencoding())(sys.stdout.buffer)
            except AttributeError: # Python 2
                sys.stdout = codecs.getwriter(locale.getpreferredencoding())(sys.stdout)

        usage = '%(prog)s [options] [command] [-- [command options]]'
        parser = argparse.ArgumentParser(usage=usage)
        parser.add_argument('-c', '--config',
                             help=_('config file to use [default: ~/.spacecmd/config]'))
        parser.add_argument('-u', '--username',
                            help=_('use this username to connect to the server'))
        parser.add_argument('-p', '--password',
                            help=_('use this password to connect to the server (insecure). Use config instead or spacecmd will ask.'))
        parser.add_argument('-s', '--server',
                            help=_('connect to this server [default: local hostname]'))
        parser.add_argument('--nossl', action='store_true',
                            help=_('use HTTP instead of HTTPS'))
        parser.add_argument('--nohistory', action='store_true',
                            help=_('do not store command history'))
        parser.add_argument('-y', '--yes', action='store_true',
                            help=_('answer yes for all questions'))
        parser.add_argument('-q', '--quiet', action='store_true',
                            help=_('print only error messages'))
        parser.add_argument('-d', '--debug', action='count', default=0,
                            help=_('print debug messages (can be passed multiple times)'))
        parser.add_argument('command', nargs='*',
                            help=argparse.SUPPRESS)

        options = parser.parse_args()
        if options.command:
            args = options.command
        else:
            args = []

        # determine the logging level
        if options.debug:
            level = logging.DEBUG
        elif options.quiet:
            level = logging.ERROR
        else:
            level = logging.INFO

        # configure logging
        logging.basicConfig(level=level, format='%(levelname)s: %(message)s')

        # files are loaded from ~/.spacecmd/
        conf_dir = os.path.expanduser('~/.spacecmd')
        user_conf_file = os.path.join(conf_dir, 'config')

        # server-specifics will be loaded from the configuration file later
        config = SafeConfigParser()

        # prevent readline from outputting escape sequences to non-terminals
        if not sys.stdout.isatty():
            logging.debug('stdout is not a TTY, setting TERM=dumb')
            os.environ['TERM'] = 'dumb'

        # import our Cmd subclass after we settle our TERM value
        from spacecmd.shell import SpacewalkShell, UnknownCallException

        # create an instance of the shell
        shell = SpacewalkShell(options, conf_dir, config)

        # set the default server to local hostname
        if shell.options.server:
            shell.config['server'] = shell.options.server
        else:
            shell.config['server'] = get_localhost_fqdn()

        # don't automatically create config files passed via --config
        if shell.options.config:
            if not os.path.isfile(shell.options.config):
                logging.error(_N('Config file %s does not exist.'), shell.options.config)
                sys.exit(1)
        else:
        # create an empty configuration file if one's not present
            if not os.path.isfile(user_conf_file):
                try:
                    # create ~/.spacecmd
                    if not os.path.isdir(conf_dir):
                        logging.debug('Creating %s', conf_dir)
                        os.mkdir(conf_dir, int('0700', 8))

                    # create a template configuration file
                    logging.debug('Creating configuration file: %s', user_conf_file)
                    handle = open(user_conf_file, 'w')
                    handle.write('[spacecmd]\n')
                    handle.close()
                except IOError:
                    logging.error(_N('Could not create %s'), user_conf_file)

        # load options from configuration files
        if shell.options.config:
            files_read = config.read([_SYSTEM_CONF_FILE, user_conf_file, shell.options.config])
        else:
            files_read = config.read([_SYSTEM_CONF_FILE, user_conf_file])

        for item in files_read:
            logging.debug('Read configuration from %s', item)

        # load the default configuration section
        shell.load_config_section('spacecmd')

        # run a single command from the command line
        if len(args):
            try:
                # rebuild the command and quote all arguments to be safe
                # except for help command
                command = args[0]

                if command == 'help':
                    command = ' '.join(args)
                if len(args) > 1:
                    command += ' %s' % ' '.join('%s' % s if not True in [ c.isspace() for c in s ] else "'%s'" % s for s in args[1:])

                # run the command
                precmd = shell.precmd(command)
                if precmd == '':
                    sys.exit(1)
                result = shell.onecmd(precmd)
                logging.debug("command=%s, return_value=%s", precmd, repr(result))
                if result == 1:
                    sys.exit(result)
            except KeyboardInterrupt:
                print
                print(_('User Interrupt'))
            except UnknownCallException:
                sys.exit(1)
            except BrokenPipeError:
                # We don't want to log BrokenPipeError, and take an early exit
                sys.exit(1)
            except Exception as detail:
                # get the relevant part of a XML-RPC fault
                if isinstance(detail, xmlrpclib.Fault):
                    detail = detail.faultString

                if shell.options.debug:
                    # print(the traceback when debugging)
                    logging.exception(detail)
                else:
                    logging.error(detail)

                sys.exit(1)
        else:
            if not shell.options.quiet:
                print(_INTRO)

            if not shell.do_login(''):
                sys.exit(1)

            # stay in the interactive shell forever
            while True:
                try:
                    shell.cmdloop()
                except KeyboardInterrupt:
                    print
                except SystemExit:
                    sys.exit(0)
                except UnknownCallException:
                    pass
                except Exception as detail:
                    # get the relevant part of a XML-RPC fault
                    if isinstance(detail, xmlrpclib.Fault):
                        detail = detail.faultString

                        # the session expired
                        if re.search(_('Could not find session'), detail, re.I):
                            shell.session = ''

                    if shell.options.debug:
                        # print(the traceback when debugging)
                        logging.exception(detail)
                    else:
                        logging.error(detail)
        sys.stdout.flush()
    except SystemExit as exc:
        _redirect_stdout_to_devnull()
        sys.exit(exc.code)
    except (IOError, BrokenPipeError) as exc:
        if exc.errno == errno.EPIPE:
            _redirect_stdout_to_devnull()
        else:
            raise exc
