"""This module defines tools for application development."""

from copy import copy
try:
    from . import argparse
except ImportError:
    try:
        import argparse
    except ImportError:
        raise ImportError('You need argparse module to '
                          'be able to use ProDy apps.')
import textwrap

from prody.utilities import wrapText

__all__ = ['Quiet', 'UsageExample', 'ProDyCitation', 'ProDyVersion',
           'DevelApp']

FIGARGS = {
    ('-F', '--figure-format'): {
        'dest': 'figformat',
        'type': str,
        'default': 'pdf',
        'metavar': 'STR',
        'help': 'figure file format',
        'choices': set('eps pdf png ps raw rgba svg svgz'.split())},
    ('-D', '--dpi'): {
        'dest': 'figdpi',
        'type': int,
        'default': 300,
        'metavar': 'INT',
        'help': 'figure resolution (dpi)'},
    ('-W', '--width'): {
        'dest': 'figwidth',
        'type': float,
        'default': 8,
        'metavar': 'FLOAT',
        'help': 'figure width (inch)'},
    ('-H', '--height'): {
        'dest': 'figheight',
        'type': float,
        'default': 6,
        'metavar': 'FLOAT',
        'help': 'figure height (inch)'},
}


class Quiet(argparse.Action):

    def __call__(self, parser, namespace, values, option_string=None):

        import prody
        prody.LOGGER.verbosity = 'warning'


class UsageExample(argparse.Action):

    def __call__(self, parser, namespace, values, option_string=None):

        tw = textwrap.TextWrapper()
        lines = [line.strip() for line in
                 namespace.usage_example.splitlines()]
        buff = ''
        for line in lines:
            if not line and buff:
                print('\n'.join(tw.wrap(buff)))
                print('')
                buff = ''
            elif line.startswith('$'):
                print('  ' + line)
                print('')
            else:
                if buff:
                    buff += '  ' if buff.endswith('.') else ' '
                buff += line

        parser.exit()


class ProDyCitation(argparse.Action):

    def __call__(self, parser, namespace, values, option_string=None):

        print("Bakan A, Meireles LM, Bahar I "
              "ProDy: Protein Dynamics Inferred from Theory and Experiments "
              "Bioinformatics 2011 27(11):1575-1577.\n"
              "Bakan A, Dutta A, Mao W, Liu Y, Chennubhotla C, Lezon TR, Bahar I "
              "Evol and ProDy for Bridging Protein Sequence Evolution and Structural Dynamics "
              "Bioinformatics 2014 30(18):2681-2683.\n"
              "Zhang S, Krieger JM, Zhang Y, Kaya C, Kaynak B, Mikulska-Ruminska K, Doruker P, Li H, Bahar I "
              "ProDy 2.0: increased scale and scope after 10 years of protein dynamics modelling with Python "
              "Bioinformatics 2021 30(18):2681-2683.")
        parser.exit()


class ProDyVersion(argparse.Action):

    def __call__(self, parser, namespace, values, option_string=None):

        import prody
        print("ProDy " + prody.__version__)
        parser.exit()


class DevelApp(object):

    def __init__(self, name, help):

        self._name = name
        self._help = help
        self._groups = []
        self._group_desc = {}
        self._group_args = {'positional': [], 'ungrouped': [], }
        self._args = {}
        self._example = None
        self._egtests = None
        self._function = None
        self._figures = []
        self._figargs = []

    def _getKwargs(self, arg):
        """Returns keyword arguments."""

        kwargs = copy(self._args[arg])
        default = kwargs.get('default')
        help = ''
        if 'choices' in kwargs:
            help = ', one of ' + ', '.join([str(ch)
                                            for ch in kwargs['choices']])

        if default is not None and 'action' not in kwargs:
            help += ' (default: %(default)s)'.format()
        kwargs['help'] += help
        return kwargs

    def _docArg(self, doc, arg):
        """Add documentation string for *arg* to *doc*."""

        kwargs = self._args[arg]
        dest = kwargs.get('dest', arg[0])
        desc = ':arg {0}: {1}'.format(dest, kwargs['help'])
        choices = kwargs.get('choices')
        if choices:
            desc += ', one of ' + ', '.join(['``' + repr(ch) + '``'
                                             for ch in choices])
        default = kwargs.get('default')
        if default:
            desc += ', default is ``{0}``'.format(repr(default))
        doc.extend(wrapText(desc, join=False, subsequent_indent='    '))
        try:
            type = kwargs['type']
        except KeyError:
            try:
                action = kwargs['action']
            except KeyError:
                type = None
            else:
                if action.startswith('store') and action.endswith('e'):
                    type = bool
                else:
                    type = None
        if type is not None:
            doc.append(':type {0}: {1}'.format(dest, type.__name__))
        doc.append('')

    def addGroup(self, name, description, mutexc=False, required=False):
        """If *mutexc* is **True** add a mutually exclusive group. """

        if name in self._group_args:
            raise ValueError('group {0} is already defined'.format(name))

        self._groups.append((name, mutexc, required))
        self._group_desc[name] = description
        self._group_args[name] = []

    def addArgument(self, *args, **kwargs):

        if args in self._args:
            raise ValueError('argument {0} is already defined'
                             .format(str(args)))
        if len(args) == 1 and args[0][0] != '-':
            group = 'positional'
        else:
            group = kwargs.pop('group', 'ungrouped')
            #if 'choices' in kwargs and 'default' not in kwargs:
            #    raise ValueError('argument has multiple choices, '
            #                     'but no default value')
        self._group_args[group].append(args)
        self._args[args] = kwargs

    def addFigure(self, *args, **kwargs):
        """Add figure argument.  Figure options will be displayed at the end.
        When multiple figures are added, ``-A, --all-figures`` option will
        be added automatically."""

        if args in self._args:
            raise ValueError('argument {0} is already defined'
                             .format(str(args)))
        if args[0][0] != '-':
            raise ValueError('figure argument cannot be a positional argument')

        if not self._figures:
            self._args.update(FIGARGS)

        self._figures.append(args)
        self._args[args] = kwargs

    def addFigarg(self, *args, **kwargs):

        if args in self._args:
            raise ValueError('argument {0} is already defined'
                             .format(str(args)))
        if args[0][0] != '-':
            raise ValueError('figure argument cannot be a positional argument')

        self._figargs.append(args)
        self._args[args] = kwargs

    def setExample(self, example, tests=None):
        """Set usage *example* string and list of examples for *tests*."""

        self._example = example
        self._egtests = tests or []

    def addApplication(self, parser):

        if self._function is None:
            raise ValueError('an application function is not set')

        sub = parser.add_parser(self._name, help=self._help)

        sub.add_argument('--quiet', help="suppress info messages to stderr",
            action=Quiet, nargs=0)
        #sub.add_argument('--debug', help="print error traceback to screen",
        #    action='store_true', dest='debug')

        if self._example:
            sub.add_argument('--examples', action=UsageExample, nargs=0,
                help='show usage examples and exit')
            sub.set_defaults(usage_example=self._example,
                             test_examples=self._egtests)

        for arg in self._group_args['positional']:
            sub.add_argument(*arg, **self._args[arg])
        for arg in self._group_args['ungrouped']:
            sub.add_argument(*arg, **self._getKwargs(arg))
        for name, mutexc, required in self._groups:
            if mutexc:
                group = sub.add_argument_group(self._group_desc[name])
                group = group.add_mutually_exclusive_group(required=required)
            else:
                group = sub.add_argument_group(self._group_desc[name])
            for arg in self._group_args[name]:
                group.add_argument(*arg, **self._getKwargs(arg))

        if self._figures:
            if len(self._figures) > 1:
                group = sub.add_argument_group('figures')
                group.add_argument('-A', '--all-figures', dest='figall',
                    action='store_true', default=False)
            else:
                group = sub.add_argument_group('figure options')

            for arg in self._figures:
                group.add_argument(*arg, **self._getKwargs(arg))

            if len(self._figures) > 1:
                group = sub.add_argument_group('figure options')

            for arg in self._figargs:
                group.add_argument(*arg, **self._getKwargs(arg))

            for arg in list(FIGARGS.keys()):
                group.add_argument(*arg, **self._getKwargs(arg))

        positional = [(arg[0], self._args[arg].get('nargs', None))
                      for arg in self._group_args['positional']]

        def callback(ns, func=self._function, pos=positional):
            args = []
            for arg, nargs in pos:
                if nargs is None:
                    args.append(ns.__dict__.pop(arg))
                else:
                    args.extend(ns.__dict__.pop(arg))
            try:
                func(*args, **ns.__dict__)
            except Exception as err:
                import sys, traceback, tempfile
                traceback.print_exc(file=sys.stderr)
                sys.stderr.write('An exception occurred when executing '
                 'your command.  If this is not a user error, please '
                 'report it to ProDy developers at: '
                 'https://github.com/prody/ProDy/issues\n')
                sys.stderr.write('Error: ' + str(err) + '\n')

        sub.set_defaults(func=callback)
        sub.set_defaults(subparser=sub)

    def addDocstring(self, function, help=True):
        """Add documentation string to *function* function.  If *help* is
        **True** help text will also be added."""

        doc = []
        if help:
            help = self._help
            for arg in self._group_args['positional']:
                arg = arg[0]
                help = help.replace(arg, '*' + arg + '*')
            doc.append(help[0].upper() + help[1:] + '.')
            doc.append('')

        for arg in self._group_args['positional']:
            self._docArg(doc, arg)
        for arg in self._group_args['ungrouped']:
            self._docArg(doc, arg)
        for name, _, _ in self._groups:
            doc.append('*' + self._group_desc[name].title() + '*')
            doc.append('')
            for arg in self._group_args[name]:
                self._docArg(doc, arg)

        function.__doc__ = '\n'.join(doc)

    def setFunction(self, function, docstring=True):
        """Set *function*, and add *docstring* based on argument settings.
        Function should be set after all arguments are added."""

        self._function = function
        if docstring:
            self.addDocstring(function)
