"""MagicHelper - dockable widget showing magic commands for the MainWindow
"""

# Copyright (c) IPython Development Team.
# Distributed under the terms of the Modified BSD License. 

#-----------------------------------------------------------------------------
# Imports
#-----------------------------------------------------------------------------

# stdlib imports
import json
import re
import sys

# System library imports
from IPython.external.qt import QtGui,QtCore

from IPython.core.magic import magic_escapes

class MagicHelper(QtGui.QDockWidget):
    """MagicHelper - dockable widget for convenient search and running of
                     magic command for IPython QtConsole.
    """

    #---------------------------------------------------------------------------
    # signals
    #---------------------------------------------------------------------------

    pasteRequested = QtCore.Signal(str, name = 'pasteRequested')
    """This signal is emitted when user wants to paste selected magic 
       command into the command line.
    """

    runRequested = QtCore.Signal(str, name = 'runRequested')
    """This signal is emitted when user wants to execute selected magic command
    """

    readyForUpdate = QtCore.Signal(name = 'readyForUpdate')
    """This signal is emitted when MagicHelper is ready to be populated.
       Since kernel querying mechanisms are out of scope of this class,
       it expects its owner to invoke MagicHelper.populate_magic_helper()
       as a reaction on this event.
    """

    #---------------------------------------------------------------------------
    # constructor
    #---------------------------------------------------------------------------

    def __init__(self, name, parent):
        super(MagicHelper, self).__init__(name, parent)

        self.data = None

        class MinListWidget(QtGui.QListWidget):
            """Temp class to overide the default QListWidget size hint
               in order to make MagicHelper narrow
            """
            def sizeHint(self):
                s = QtCore.QSize()
                s.setHeight(super(MinListWidget,self).sizeHint().height())
                s.setWidth(self.sizeHintForColumn(0))
                return s

        # construct content
        self.frame = QtGui.QFrame()
        self.search_label = QtGui.QLabel("Search:")
        self.search_line = QtGui.QLineEdit()
        self.search_class = QtGui.QComboBox()
        self.search_list = MinListWidget()
        self.paste_button = QtGui.QPushButton("Paste")
        self.run_button = QtGui.QPushButton("Run")
        
        # layout all the widgets
        main_layout = QtGui.QVBoxLayout()
        search_layout = QtGui.QHBoxLayout()
        search_layout.addWidget(self.search_label)
        search_layout.addWidget(self.search_line, 10)
        main_layout.addLayout(search_layout)
        main_layout.addWidget(self.search_class)
        main_layout.addWidget(self.search_list, 10)
        action_layout = QtGui.QHBoxLayout()
        action_layout.addWidget(self.paste_button)
        action_layout.addWidget(self.run_button)
        main_layout.addLayout(action_layout)

        self.frame.setLayout(main_layout)
        self.setWidget(self.frame)

        # connect all the relevant signals to handlers
        self.visibilityChanged[bool].connect( self._update_magic_helper )
        self.search_class.activated[int].connect( 
            self.class_selected
        )
        self.search_line.textChanged[str].connect(
            self.search_changed
        )
        self.search_list.itemDoubleClicked.connect(
            self.paste_requested
        )
        self.paste_button.clicked[bool].connect(
            self.paste_requested
        )
        self.run_button.clicked[bool].connect(
            self.run_requested
        )

    #---------------------------------------------------------------------------
    # implementation
    #---------------------------------------------------------------------------

    def _update_magic_helper(self, visible):
        """Start update sequence. 
           This method is called when MagicHelper becomes visible. It clears
           the content and emits readyForUpdate signal. The owner of the 
           instance is expected to invoke populate_magic_helper() when magic
           info is available.
        """
        if not visible or self.data is not None:
            return
        self.data = {}
        self.search_class.clear()
        self.search_class.addItem("Populating...")
        self.search_list.clear()
        self.readyForUpdate.emit()

    def populate_magic_helper(self, data):
        """Expects data returned by lsmagics query from kernel.
           Populates the search_class and search_list with relevant items.
        """
        self.search_class.clear()
        self.search_list.clear()
                
        self.data = json.loads(
            data['data'].get('application/json', {})
        )
        
        self.search_class.addItem('All Magics', 'any')
        classes = set()

        for mtype in sorted(self.data):
            subdict = self.data[mtype]
            for name in sorted(subdict):
                classes.add(subdict[name])

        for cls in sorted(classes):
            label = re.sub("([a-zA-Z]+)([A-Z][a-z])","\g<1> \g<2>", cls)
            self.search_class.addItem(label, cls)

        self.filter_magic_helper('.', 'any')

    def class_selected(self, index):
        """Handle search_class selection changes
        """
        item = self.search_class.itemData(index)
        regex = self.search_line.text()
        self.filter_magic_helper(regex = regex, cls = item)

    def search_changed(self, search_string):
        """Handle search_line text changes.
           The text is interpreted as a regular expression
        """
        item = self.search_class.itemData(
            self.search_class.currentIndex()
        )
        self.filter_magic_helper(regex = search_string, cls = item)

    def _get_current_search_item(self, item = None):
        """Retrieve magic command currently selected in the search_list
        """
        text = None
        if not isinstance(item, QtGui.QListWidgetItem):
            item = self.search_list.currentItem()        
        text = item.text()
        return text

    def paste_requested(self, item = None):
        """Emit pasteRequested signal with currently selected item text
        """
        text = self._get_current_search_item(item)
        if text is not None:
            self.pasteRequested.emit(text)

    def run_requested(self, item = None):
        """Emit runRequested signal with currently selected item text
        """
        text = self._get_current_search_item(item)
        if text is not None:
            self.runRequested.emit(text)

    def filter_magic_helper(self, regex, cls):
        """Update search_list with magic commands whose text match
           regex and class match cls.
           If cls equals 'any' - any class matches.
        """
        if regex == "" or regex is None:
            regex = '.'
        if cls is None:
            cls = 'any'

        self.search_list.clear()
        for mtype in sorted(self.data):
            subdict = self.data[mtype]
            prefix = magic_escapes[mtype]

            for name in sorted(subdict):
                mclass = subdict[name]
                pmagic = prefix + name

                if (re.match(regex, name) or re.match(regex, pmagic)) and \
                   (cls == 'any' or cls == mclass): 
                    self.search_list.addItem(pmagic)

