# -*- coding: utf-8 -*-
#
# Copyright (C) 2015-2016 Hewlett Packard Enterprise Development LP
#
# 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.

"""
topology module for interactive mode.
"""

from __future__ import unicode_literals, absolute_import
from __future__ import print_function, division

import logging
from atexit import register
from inspect import ismodule
from traceback import format_exc
from os.path import join, expanduser, isfile


log = logging.getLogger(__name__)


def catch_exc(func):
    """
    Decorator for in-shell exception handling.

    This is particularly required for ``readline`` hooks, as they run in the
    C library and cannot catch any Python exception ocurring in them.
    """
    def catcher(*args):
        try:
            return func(*args)
        except:
            log.error(format_exc())
    return catcher


class NamespaceCompleter(object):
    """
    Readline completer using a dictionary namespace.
    """

    def __init__(self, ns):
        self.matches = []
        self.ns = ns

    def search_tree(self, root, path):
        """
        Search for a node in a Python object as if it were a tree: namespaces,
        modules, objects, method, attributes, etc.

        :param root: The root Python object.
        :param list path: List of names representing the path.
        :rtype: list
        :return: The subnode or None if not found.
        """
        node = root

        for p in path:

            attrs = self.dict_attributes(node)

            if p not in attrs:
                node = None
                break

            node = attrs[p]

        return node

    def dict_attributes(self, element):
        """
        Type aware attributes to dictionary function.

        :param element: Python object to list attributes.
        :rtype: dict
        :return: A dict of the element attributes.
        """
        if element is not None:

            # Element is a dictionary, return itself
            if isinstance(element, dict):
                return element

            # Element is a module as has a __all__ defined
            if ismodule(element) and hasattr(element, '__all__'):
                return {
                    k: v for k, v in vars(element).items()
                    if k in element.__all__
                }

            # The element can be converted to dict
            if hasattr(element, '__dict__'):
                return vars(element)

        return {}

    def format_matches(self, path, attrs, prefixed):
        """
        Format a list of attributes to be shown in autocompleter.

        :param path: List of string representing the path to the node.
        :param attrs: List of attributes of the node.
        :param prefixed: Prefix attributes must have in order to be shown.
        :rtype: list
        :return: Formatted list of attributes.
        """
        # Filter super private attributes
        attrs = [a for a in attrs if not a.startswith('__')]

        # Filter attributes if a prefix is required
        if prefixed:
            attrs = [a for a in attrs if a.startswith(prefixed)]

        return ['.'.join(path + [a]) for a in attrs]

    @catch_exc
    def complete(self, text, state):

        if state == 0:  # On first trigger, build possible matches

            path = text.split('.')
            search = path.pop()

            subnode = self.search_tree(self.ns, path)
            attributes = sorted(self.dict_attributes(subnode))
            self.matches = self.format_matches(path, attributes, search)

        # Return match indexed by state
        try:
            return self.matches[state]
        except IndexError:
            return None


def interact(mgr):
    """
    Shell setup function.

    This function will setup the library, create the default namespace with
    shell symbols, setup the history file, the autocompleter and launch a shell
    session.
    """
    print('Engine nodes available for communication:')
    print('    {}'.format(', '.join(mgr.nodes.keys())))

    # Create and populate shell namespace
    ns = {
        'topology': mgr
    }
    for key, enode in mgr.nodes.items():
        ns[key] = enode

    # Configure readline, history and autocompleter
    import readline

    histfile = join(expanduser('~'), '.topology_history')
    if isfile(histfile):
        try:
            readline.read_history_file(histfile)
        except IOError:
            log.error(format_exc())
    register(readline.write_history_file, histfile)

    completer = NamespaceCompleter(ns)
    readline.set_completer(completer.complete)
    readline.parse_and_bind('tab: complete')

    # Python's Interactive
    from code import interact as pyinteract
    pyinteract('', None, ns)


__all__ = ['NamespaceCompleter', 'interact']
