# -*- coding: utf-8 -*-
#
# Copyright © Spyder Project Contributors
# Licensed under the terms of the MIT License
# (see spyder/__init__.py for details)

"""
Widget that handles communications between the IPython Console and
the Help plugin
"""

# Standard library imports
from __future__ import absolute_import

import re

# Third party imports
from pickle import UnpicklingError
from qtconsole.ansi_code_processor import ANSI_OR_SPECIAL_PATTERN, ANSI_PATTERN
from qtconsole.rich_jupyter_widget import RichJupyterWidget
from qtpy.QtCore import QEventLoop

# Local imports
from spyder.py3compat import TimeoutError
from spyder_kernels.utils.dochelpers import (getargspecfromtext,
                                             getsignaturefromtext)


class HelpWidget(RichJupyterWidget):
    """
    Widget with the necessary attributes and methods to handle communications
    between the IPython Console and the Help plugin
    """

    def clean_invalid_var_chars(self, var):
        """
        Replace invalid variable chars in a string by underscores

        Taken from https://stackoverflow.com/a/3305731/438386
        """
        return re.sub(r'\W|^(?=\d)', '_', var)

    def get_documentation(self, content):
        """Get documentation from inspect reply content."""
        data = content.get('data', {})
        text = data.get('text/plain', '')
        if text:
            if (self.language_name is not None
                    and self.language_name == 'python'):
                text = re.compile(ANSI_PATTERN).sub('', text)
                signature = self.get_signature(content).split('(')[-1]

                # Base value for the documentation
                documentation = (text.split('Docstring:')[-1].
                                 split('Type:')[0].split('File:')[0])

                if signature:
                    # Check if the signature is in the Docstring
                    doc_from_signature = documentation.split(signature)
                    if len(doc_from_signature) > 1:
                        return (doc_from_signature[-1].split('Docstring:')[-1].
                                split('Type:')[0].
                                split('File:')[0]).strip('\r\n')

                return documentation.strip('\r\n')
            else:
                text = re.compile(ANSI_PATTERN).sub('', text)
                return text.strip('\r\n')
        else:
            return ''

    def _get_signature(self, name, text):
        """Get signature from text using a given function name."""
        signature = ''
        argspec = getargspecfromtext(text)
        if argspec:
            # This covers cases like np.abs, whose docstring is
            # the same as np.absolute and because of that a proper
            # signature can't be obtained correctly
            signature = name + argspec
        else:
            signature = getsignaturefromtext(text, name)
        return signature

    def get_signature(self, content):
        """Get signature from inspect reply content"""
        data = content.get('data', {})
        text = data.get('text/plain', '')
        if text:
            if (self.language_name is not None
                    and self.language_name == 'python'):
                self._control.current_prompt_pos = self._prompt_pos
                line = self._control.get_current_line_to_cursor()
                name = line[:-1].split('(')[-1]   # Take last token after a (
                name = name.split('.')[-1]   # Then take last token after a .

                # Clean name from invalid chars
                try:
                    name = self.clean_invalid_var_chars(name)
                except Exception:
                    pass

                text = text.split('Docstring:')

                # Try signature from text before 'Docstring:'
                before_text = text[0]
                before_signature = self._get_signature(name, before_text)

                # Try signature from text after 'Docstring:'
                after_text = text[-1]
                after_signature = self._get_signature(name, after_text)

                # Stay with the longest signature
                if len(before_signature) > len(after_signature):
                    signature = before_signature
                else:
                    signature = after_signature

                # Prevent special characters. Applied here to ensure
                # recognizing the signature in the logic above.
                signature = ANSI_OR_SPECIAL_PATTERN.sub('', signature)

                return signature.strip('\r\n')
            else:
                text = re.compile(ANSI_PATTERN).sub('', text)
                return text.strip('\r\n')
        else:
            return ''

    def is_defined(self, objtxt, force_import=False):
        """Return True if object is defined"""
        try:
            return self.call_kernel(
                interrupt=True, blocking=True
                ).is_defined(objtxt, force_import=force_import)
        except (TimeoutError, UnpicklingError, RuntimeError):
            return None

    def get_doc(self, objtxt):
        """Get object documentation dictionary"""
        try:
            return self.call_kernel(interrupt=True, blocking=True
                                    ).get_doc(objtxt)
        except (TimeoutError, UnpicklingError, RuntimeError):
            return None

    def get_source(self, objtxt):
        """Get object source"""
        try:
            return self.call_kernel(interrupt=True, blocking=True
                                    ).get_source(objtxt)
        except (TimeoutError, UnpicklingError, RuntimeError):
            return None

    #---- Private methods (overrode by us) ---------------------------------
    def _handle_inspect_reply(self, rep):
        """
        Reimplement call tips to only show signatures, using the same
        style from our Editor and External Console too
        """
        cursor = self._get_cursor()
        info = self._request_info.get('call_tip')
        if (info and info.id == rep['parent_header']['msg_id'] and
                info.pos == cursor.position()):
            content = rep['content']
            if content.get('status') == 'ok' and content.get('found', False):
                signature = self.get_signature(content)
                documentation = self.get_documentation(content)
                new_line = (self.language_name is not None
                            and self.language_name == 'python')
                self._control.show_calltip(
                    signature,
                    documentation=documentation,
                    language=self.language_name,
                    max_lines=7,
                    text_new_line=new_line
                )
