__author__ = 'maartenbreddels'
import string
import astropy.io.votable.ucd
import astropy.units
import logging
from vaex.ui.qt import *
# from astropy.utils import data
import astropy.utils.data
# astropy.utils.data
import numpy as np

logger = logging.getLogger("vaex.ui.completer")

# based on http://stackoverflow.com/a/26065682


class Completer(QtGui.QCompleter):
    def __init__(self, line_edit, allowed_chars=string.ascii_letters + string.digits + "_", separators=None, match_contains=True, match_case=False):
        QtGui.QCompleter.__init__(self, [], line_edit)
        self.line_edit = line_edit
        self.match_contains = match_contains
        self.match_case = match_case
        self.allowed_chars = allowed_chars
        self.separators = separators
        self.line_edit.cursorPositionChanged.connect(self.onCursorPositionChanged)
        self.last_editing_cursor_position = None
        self.activated.connect(self.onActivated)
        self.setWrapAround(False)
        self.setCompletionMode(QtGui.QCompleter.UnfilteredPopupCompletion)
        self.setCaseSensitivity(QtCore.Qt.CaseInsensitive)
        # self.setCompletionMode(QtGui.QCompleter.InlineCompletion)

        self.model = QtGui.QStringListModel([], self)
        self.setModel(self.model)

    def pathFromIndex(self, index):
        # return QtGui.QCompleter.pathFromIndex(self, index)
        suggested_word = QtGui.QCompleter.pathFromIndex(self, index)

        full_text = self.line_edit.text()

        index = self.line_edit.cursorPosition()
        left, right = self.find_word_bounds(full_text, index, self.allowed_chars)

        suggested_text = full_text[:left] + suggested_word + full_text[right:]
        new_cursor_pos = len(full_text[:left] + suggested_word)

        logger.debug("cursor should go to: %d", new_cursor_pos)

        def fixcursor():
            logger.debug("cursor set to: %d", new_cursor_pos)
            self.line_edit.setCursorPosition(new_cursor_pos)
        # print(("pathFromIndex", index, repr(full_text), repr(suggested_text), repr(suggested_text), self.last_editing_cursor_position))

        # after the text is set by completer, the cursor is set to the end of the lineedit, we correct is by fixcursor to set it at
        # the end of the word
        QtCore.QTimer.singleShot(0, fixcursor)
        return suggested_text

    def splitPath(self, path):
        index = self.line_edit.cursorPosition()
        left, right = self.find_word_bounds(path, index, self.allowed_chars)
        part = path[left:right]

        result = QtGui.QCompleter.splitPath(self, path)
        # print "splitPath", path, result, part

        suggestions = self.get_suggestions(part, path[:left], path[right:])
        self.suggestions = suggestions
        self.model.setStringList(suggestions)
        # self.model = QtGui.QStringListModel(suggestions, self.parent())
        # self.setModel(self.model)
        # QtCore.QTimer.singleShot(0, lambda: self.setModel(self.model));
        self.parts = [part]
        return self.parts

    def onActivated(self, text):
        pass  # print "activated", text

    def onCursorPositionChanged(self, old_pos, new_pos):
        # print "cursor", old_pos, new_pos
        # this trick didn't work, as suggested by the SO anwser
        self.last_editing_cursor_position = None if old_pos == new_pos else new_pos

    def word_boundary_char(self, char):
        return (self.allowed_chars is not None and char not in self.allowed_chars) or\
            (self.separators is not None and char in self.separators)

    def find_word_bounds(self, text, index, allowed_chars):
        right = left = index
        done = False
        while not done:
            if left == 0:
                done = True
            elif not self.word_boundary_char(text[left - 1]):
                left -= 1
            else:
                done = True
        done = False
        while not done:
            if right == len(text):
                done = True
            elif not self.word_boundary_char(text[right]):
                right += 1
            else:
                done = True
        return left, right

    def get_word_list(self, word, text_left, text_right):
        return "aap aardappel schaap koe blaat".split()

    def get_suggestions(self, typed_word, text_left, text_right):
        def case(word):
            return word if self.match_case else word.lower()

        suggestions = []
        if typed_word:
            word_list = self.get_word_list(typed_word, text_left, text_right)
            for word in word_list:

                if (self.match_contains and case(typed_word) in case(word)) or (not self.match_contains and case(word).startswith(case(typed_word))):
                    suggestions.append(word)
        return suggestions


ucd_words = astropy.io.votable.ucd.UCDWords()
primary_list = list(sorted(ucd_words._primary))
secondary_list = list(sorted(ucd_words._secondary))


class UCDCompleter(Completer):
    """
    UCDs have primary words (that come first), and secondary, that come after the second
    UCD words are seperated by a ; char
    """

    def __init__(self, line_edit):
        Completer.__init__(self, line_edit, allowed_chars=None, separators=";")

    def get_word_list(self, word, text_left, text_right):
        if text_left.strip():
            return secondary_list
        else:
            return primary_list

    def get_suggestions(self, typed_word, text_left, text_right):
        typed_word = typed_word.lower()
        if text_left.strip():
            word_list = secondary_list
        else:
            word_list = primary_list
        descriptions = {key: desc.lower() for key, desc in ucd_words._descriptions.items() if key in word_list}
        suggestions = []
        if typed_word:
            for word in word_list:
                # if any([typed_word in word for word in word_list]) or any([typed_word in desc for desc in descriptions]):
                # print(typed_word)
                # print(list([typed_word in desc for desc in descriptions]))
                # print(any([typed_word in desc for desc in descriptions]))
                # print([desc for desc in descriptions if typed_word in desc])
                if (typed_word in word) or typed_word in descriptions[word]:
                    suggestions.append(ucd_words._capitalization[word])
        return suggestions


class IdentifierCompleter(Completer):
    """Completes variables and functions"""

    def __init__(self, line_edit, variables=[]):
        self.variables = variables
        Completer.__init__(self, line_edit)

    def get_word_list(self, word, text_left, text_right):
        return self.variables


unit_list = [name for name, unit in vars(astropy.units).items() if isinstance(unit, astropy.units.UnitBase)]


class UnitCompleter(Completer):
    """Completes units found in astropy"""

    def __init__(self, line_edit, unit_list=unit_list):
        self.unit_list = unit_list
        Completer.__init__(self, line_edit, match_contains=False)

    def get_word_list(self, word, text_left, text_right):
        return self.unit_list


# based on from https://gist.github.com/Riateche/5984815
class UCDDelegate(QtGui.QItemDelegate):
    def __init__(self, parent):
        QtGui.QItemDelegate.__init__(self, parent)

    def createEditor(self, parent, option, index):
        editor = QtGui.QLineEdit(parent)
        completer = vaex.ui.completer.UCDCompleter(editor)
        editor.setCompleter(completer)
        # self.connect(combo, QtCore.SIGNAL("currentIndexChanged(int)"), self, QtCore.SLOT("currentIndexChanged()"))
        return editor

    def setEditorData(self, editor, index):
        editor.blockSignals(True)
        # editor.setCurrentIndex(int(index.model().data(index)))
        editor.setText(index.model().data(index))
        editor.blockSignals(False)

    def setModelData(self, editor, model, index):
        model.setData(index, editor.text())

    def currentIndexChanged(self):
        self.commitData.emit(self.sender())


class UnitDelegate(QtGui.QStyledItemDelegate):
    def __init__(self, parent):
        QtGui.QStyledItemDelegate.__init__(self, parent)
        self.lastEditor = None

    def createEditor(self, parent, option, index):
        self.lastEditor = editor = QtGui.QLineEdit(parent)
        self.completer = vaex.ui.completer.UnitCompleter(editor)
        editor.setCompleter(self.completer)
        # self.connect(combo, QtCore.SIGNAL("currentIndexChanged(int)"), self, QtCore.SLOT("currentIndexChanged()"))

        return editor

    def setEditorData(self, editor, index):
        editor.blockSignals(True)
        # editor.setCurrentIndex(int(index.model().data(index)))
        editor.setText(index.model().data(index))
        editor.blockSignals(False)

    def setModelData(self, editor, model, index):
        model.setData(index, editor.text())

    def currentIndexChanged(self):
        self.commitData.emit(self.sender())


vars = dir(np)
# vars.append("hoeba")


class LineCompleter(QtGui.QLineEdit):
    def __init__(self, parent):
        QtGui.QLineEdit.__init__(self, parent)
        # completer = UCDCompleter(self)
        # assert completer.word_boundary_char(";")
        completer = UnitCompleter(self)
        self.setCompleter(completer)


class ExpressionCombobox(QtGui.QComboBox):
    def __init__(self, parent, dataset, variables=False):
        """

        :param parent:
        :param Dataset dataset:
        """
        QtGui.QComboBox.__init__(self, parent)
        self.identifiers = []
        self.columns = dataset.get_column_names(virtual=True)
        self.identifiers.extend(vaex.dataset.expression_namespace.keys())
        self.identifiers.extend(self.columns)
        if variables:
            self.identifiers.extend(list(dataset.variables.keys()))
        self.addItems([""] + self.columns)
        # = list(dataset.variables.keys()) + list(vaex.dataset.expression_namespace.keys())
        # self.addItems(list(dataset.variables.keys()))
        # else:
        # self.identifiers = list(self.columns) + list(vaex.dataset.expression_namespace.keys())
        self.setEditable(True)
        lineEdit = self.lineEdit()
        self.completer = IdentifierCompleter(lineEdit, self.identifiers)
        lineEdit.setCompleter(self.completer)


if __name__ == "__main__":
    app = QtGui.QApplication([])
    dialog = QtGui.QDialog()
    dialog.resize(400, 100)
    if 1:
        lineEdit = LineCompleter(dialog)
        # combo = QtGui.QComboBox(dialog)
        # combo.setEditable(True)
        # combo.addItems("hoeba blaat schaap".split())
        # lineEdit = combo.lineEdit()
        # completer = MathCompleter(lineEdit, vars)
        completer = UnitCompleter(lineEdit)
        lineEdit.setCompleter(completer)
    else:
        dataset = vaex.open(sys.argv[1])
        box = ExpressionCombobox(dialog, dataset)
    dialog.show()
    dialog.raise_()
    dialog.exec_()
    # app.exec_()
