from __future__ import print_function
# -*- coding: utf-8 -*-
import math
import collections
import time
import sys
import logging
import psutil
import traceback as tb
import vaex
import smtplib
import platform
import getpass
import os
# import urllib.request, urllib.parse, urllib.error
import vaex.utils
# from email.mime.text import MIMEText

try:
    from urllib.request import urlopen
    from urllib.parse import urlparse, urlencode
except ImportError:
    from urlparse import urlparse
    from urllib import urlopen, urlencode, quote as urlquote

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

try:
    import sip
    sip.setapi('QVariant', 2)
    sip.setapi('QString', 2)
    from PyQt4 import QtGui, QtCore, QtTest  # , QtNetwork
    sip.setapi('QVariant', 2)
    # from PyQt4.QtWebKit import QWebView
    qt_version = QtCore.PYQT_VERSION_STR
    qt_mayor = int(qt_version[0])
except ImportError as e1:
    try:
        from PyQt5 import QtGui, QtCore, QtTest, QtWidgets  # , QtNetwork

        for name in dir(QtWidgets):
            if name[0].lower() == "q":
                setattr(QtGui, name, getattr(QtWidgets, name))
        qt_version = QtCore.PYQT_VERSION_STR
        qt_mayor = int(qt_version[0])
        QtGui.QStringListModel = QtCore.QStringListModel
        # QtCore.Slot = QtCore.pyqtSlot
    except ImportError as e1b:
        try:
            from PySide import QtGui, QtCore, QtTest  # , QtNetwork

            # from PySide.QtWebKit import QWebView
            QtCore.pyqtSignal = QtCore.Signal
            qt_version = QtCore.__version__
            qt_mayor = 4
        except ImportError as e2:
            print("could not import PyQt4, PyQt5, or PySide, please install", file=sys.stderr)
            print("errors: ", repr(e1), repr(e2), file=sys.stderr)
            sys.exit(1)


def attrsetter(object, attr_name):
    def setter(value):
        setattr(object, attr_name, value)
    return setter


def attrgetter(object, attr_name):
    def getter():
        return getattr(object, attr_name)
    return getter


class ProgressExecution(object):
    def __init__(self, parent, title, cancel_text="Cancel", executor=None):
        self.parent = parent
        self.title = title
        self.cancel_text = cancel_text
        self.executor = executor
        if self.executor:
            def begin():
                self.time_begin = time.time()
                # self.progress_bar.setValue(0)
                self.cancelled = False
                # self.button_cancel.setEnabled(True)

            def end():
                # self.progress_bar.setValue(1000)
                self.cancelled = False
                # self.button_cancel.setEnabled(False)
                time_total = time.time() - self.time_begin
                # self.label_time.setText("%.2fs" % time_total)

            def progress(fraction):
                # self.progress_bar.setValue(fraction*1000)
                # QtCore.QCoreApplication.instance().processEvents()
                # logger.debug("queue: %r %r", self.queue_update.counter, self.queue_update.counter_processed)
                # return (not self.cancelled) and (not self.queue_update.in_queue(2))
                return self.progress(fraction * 100)

            def cancel():
                # self.progress_bar.setValue(0)
                # self.button_cancel.setEnabled(False)
                # self.label_time.setText("cancelled")
                pass

            # self._begin_signal = self.executor.signal_begin.connect(begin)
            # self._progress_signal = self.executor.signal_progress.connect(progress)
            # self._end_signal = self.executor.signal_end.connect(end)
            # self._cancel_signal = self.executor.signal_cancel.connect(cancel)
        self.tasks = []
        self._task_signals = []

    def execute(self):
        logger.debug("show dialog")
        if isinstance(self.executor, vaex.remote.ServerExecutor):
            self.dialog.exec_()
            logger.debug("dialog stopped")
            # important possible deadlock possible:
            # in the case where fulfill or reject is indirectly called, the call to that promise is put in a qtevent
            # so while they are pending, keep processing events until they are handled in this thread
            while any(task.isPending for task in self.tasks):
                QtCore.QCoreApplication.instance().processEvents()
                time.sleep(0.01)
            self.finished_tasks()
        else:
            self.dialog.show()
            self.executor.execute()
            self.dialog.hide()
        logger.debug("self.dialog.wasCanceled() = %r", self.dialog.wasCanceled())
        return not self.dialog.wasCanceled()

    def add_task(self, task):
        self._task_signals.append(task.signal_progress.connect(self._on_progress))
        self.tasks.append(task)
        return task

    def _on_progress(self, fraction):
        total = self.get_progress_fraction()
        self.progress(total * 100)
        QtCore.QCoreApplication.instance().processEvents()
        ok = not self.dialog.wasCanceled()
        if total == 1:
            self.dialog.hide()
        return ok

    def get_progress_fraction(self):
        total_fraction = 0
        for task in self.tasks:
            total_fraction += task.progress_fraction
        fraction = total_fraction / len(self.tasks)
        return fraction

    def finished_tasks(self):
        for task, signal in zip(self.tasks, self._task_signals):
            task.signal_progress.disconnect(signal)
        self.tasks = []
        self._task_signals = []

    def __enter__(self):
        self.dialog = QtGui.QProgressDialog(self.title, self.cancel_text, 0, 1000, self.parent)
        # self.dialog.show()
        self.dialog.setWindowModality(QtCore.Qt.WindowModal)
        self.dialog.setMinimumDuration(0)
        self.dialog.setAutoClose(True)
        self.dialog.setAutoReset(True)
        # QtCore.QCoreApplication.instance().processEvents()
        return self

    def progress(self, percentage):
        self.dialog.setValue(int(percentage * 10))
        QtCore.QCoreApplication.instance().processEvents()
        # self.dialog.setValue(300)
        # self.dialog.update()
        # self.dialog.repaint()
        # print "progress", `percentage`, type(percentage), int(percentage*10)
        # QtCore.QCoreApplication.instance().processEvents()
        return not self.dialog.wasCanceled()
        # return False
        # raise RuntimeError("progress cancelled")

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.dialog.hide()
        if 0:  # self.executor:
            self.executor.signal_begin.disconnect(self._begin_signal)
            self.executor.signal_progress.disconnect(self._progress_signal)
            self.executor.signal_end.disconnect(self._end_signal)
            self.executor.signal_cancel.disconnect(self._cancel_signal)


class assertError(object):
    def __init__(self, calls_expected=1):
        self.calls_expected = calls_expected

    def wrapper(self, *args, **kwargs):
        self.called += 1

    def __enter__(self):
        global dialog_error
        self.remember = dialog_error
        self.called = 0
        dialog_error = self.wrapper
        logger.debug("wrapped dialog_error")

    def __exit__(self, exc_type, exc_val, exc_tb):
        global dialog_error
        assert self.called == self.calls_expected, "expected the error dialog to be invoked %i time(s), was called %i times(s)" % (self.calls_expected, self.called)
        dialog_error = self.remember
        logger.debug("unwrapped dialog_error")


class settext(object):
    def __init__(self, value, calls_expected=1):
        self.value = value
        self.calls_expected = calls_expected

    def wrapper(self, *args, **kwargs):
        self.called += 1
        return self.value

    def __enter__(self):
        global gettext
        self.remember = gettext
        self.called = 0
        gettext = self.wrapper
        logger.debug("wrapped gettext")

    def __exit__(self, exc_type, exc_val, exc_tb):
        global gettext
        assert self.called == self.calls_expected, "expected the error dialog to be invoked %i time(s), was called %i times(s)" % (self.calls_expected, self.called)
        gettext = self.remember
        logger.debug("unwrapped gettext")


class setchoose(object):
    def __init__(self, value, calls_expected=1):
        self.value = value
        self.calls_expected = calls_expected

    def wrapper(self, *args, **kwargs):
        self.called += 1
        return self.value

    def __enter__(self):
        global choose
        self.remember = choose
        self.called = 0
        choose = self.wrapper
        logger.debug("wrapped choose")

    def __exit__(self, exc_type, exc_val, exc_tb):
        global choose
        assert self.called == self.calls_expected, "expected the error dialog to be invoked %i time(s), was called %i times(s)" % (self.calls_expected, self.called)
        choose = self.remember
        logger.debug("unwrapped choose")


class FakeProgressExecution(object):
    def __init__(self, *args):
        pass

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        pass

    def progress(self, percentage):
        return True


class OptionBase(object):
    def setVisible(self, value):
        if value:
            self.show()
        else:
            self.hide()

    def show(self):
        self.combobox.show()
        self.label.show()

    def hide(self):
        self.combobox.hide()
        self.label.hide()


class Codeline(OptionBase):
    def __init__(self, parent, label, options, getter, setter, update=lambda: None):
        self.update = update
        self.options = options
        self.label = QtGui.QLabel(label, parent)
        self.combobox = QtGui.QComboBox(parent)
        self.combobox.addItems(options)
        self.combobox.setEditable(True)

        def wrap_setter(value, update=True):
            self.combobox.lineEdit().setText(value)
            setter(value)
            if update:
                self.update()
        # auto getter and setter
        setattr(self, "get_value", getter)
        setattr(self, "set_value", wrap_setter)

        def on_change(index):
            on_edit_finished()

        def on_edit_finished():
            new_value = text = str(self.combobox.lineEdit().text())
            if new_value != self.current_value:
                self.current_value = new_value
                setter(self.current_value)
                update()
        self.combobox.currentIndexChanged.connect(on_change)
        self.combobox.lineEdit().editingFinished.connect(on_edit_finished)
        # self.combobox.setCurrentIndex(options.index(getter()))
        self.current_value = getter()
        self.combobox.lineEdit().setText(self.current_value)

    def add_to_grid_layout(self, row, grid_layout):
        grid_layout.addWidget(self.label, row, 0)
        grid_layout.addWidget(self.combobox, row, 1)
        return row + 1


class Option(OptionBase):
    def __init__(self, parent, label, options, getter, setter, update=lambda: None):
        self.update = update
        self.options = options
        self.label = QtGui.QLabel(label, parent)
        self.combobox = QtGui.QComboBox(parent)
        self.combobox.addItems(options)

        def wrap_setter(value, update=True):
            self.combobox.setCurrentIndex(options.index(getter()))
            setter(value)
            if update:
                self.update()
        # auto getter and setter
        setattr(self, "get_value", getter)
        setattr(self, "set_value", wrap_setter)

        def on_change(index):
            setter(self.options[index])
            update()
        self.combobox.setCurrentIndex(options.index(getter()))
        self.combobox.currentIndexChanged.connect(on_change)

    def add_to_grid_layout(self, row, grid_layout):
        grid_layout.addWidget(self.label, row, 0)
        grid_layout.addWidget(self.combobox, row, 1)
        return row + 1


class TextOption(OptionBase):
    def __init__(self, parent, label, value, placeholder, getter, setter, update=lambda: None):
        self.update = update
        self.value = value
        self.placeholder = placeholder
        self.label = QtGui.QLabel(label, parent)
        self.textfield = QtGui.QLineEdit(parent)
        self.textfield.setPlaceholderText(self.get_placeholder())
        # self.combobox.addItems(options)

        def wrap_setter(value, update=True):
            self.textfield.setText(value)
            setter(value)
            if update:
                self.update()
        # auto getter and setter
        setattr(self, "get_value", getter)
        setattr(self, "set_value", wrap_setter)

        def on_change(*ignore):
            setter(self.textfield.text())
            update()
        # self.combobox.currentIndexChanged.connect(on_change)
        # self.combobox.setCurrentIndex(options.index(getter()))
        self.textfield.returnPressed.connect(on_change)

        if 1:
            from vaex.ui.icons import iconfile
            self.tool_button = QtGui.QToolButton(parent)
            self.tool_button .setIcon(QtGui.QIcon(iconfile('gear')))
            self.tool_menu = QtGui.QMenu()
            self.tool_button.setMenu(self.tool_menu)
            self.tool_button.setPopupMode(QtGui.QToolButton.InstantPopup)

            if self.placeholder:
                def fill(_=None):
                    value = self.get_placeholder()
                    if value:
                        setter(value)
                        self.set_value(value)
                self.action_fill = QtGui.QAction("Fill in default value", parent)
                self.action_fill.triggered.connect(fill)
                self.tool_menu.addAction(self.action_fill)

            def copy(_=None):
                value = self.textfield.text()
                if value:
                    clipboard = QtGui.QApplication.clipboard()
                    text = str(value)
                    clipboard.setText(text)
            self.action_copy = QtGui.QAction("Copy", parent)
            self.action_copy.triggered.connect(copy)
            self.tool_menu.addAction(self.action_copy)

            def paste(_=None):
                clipboard = QtGui.QApplication.clipboard()
                text = clipboard.text()
                setter(text)
                self.set_value(text)
            self.action_paste = QtGui.QAction("Paste", parent)
            self.action_paste.triggered.connect(paste)
            self.tool_menu.addAction(self.action_paste)

    def set_unit_completer(self):
        self.completer = vaex.ui.completer.UnitCompleter(self.textfield)
        self.textfield.setCompleter(self.completer)

    def get_placeholder(self):
        if callable(self.placeholder):
            return self.placeholder()
        else:
            return self.placeholder

    def add_to_grid_layout(self, row, grid_layout):
        grid_layout.addWidget(self.label, row, 0)
        grid_layout.addWidget(self.textfield, row, 1)
        grid_layout.addWidget(self.tool_button, row, 2)
        return row + 1


class RangeOption(object):
    def __init__(self, parent, label, values, getter, setter, update=lambda: None):
        self.update = update
        self.values = [str(k) for k in values]
        self.label = QtGui.QLabel(label, parent)
        self.combobox_min = QtGui.QComboBox(parent)
        self.combobox_max = QtGui.QComboBox(parent)
        self.combobox_min.setEditable(True)
        self.combobox_max.setEditable(True)
        self.vmin = None
        self.vmax = None
        # self.combobox_min.addItems(values)
        # self.combobox_max.addItems(values)

        def wrap_setter(value, update=True):
            if value is None:
                vmin, vmax = None, None
            else:
                vmin, vmax = value
            self.combobox_min.blockSignals(True)
            self.combobox_max.blockSignals(True)
            changed = False
            if vmin != self.vmin:
                # print(("setting vmin to", vmin))
                self.vmin = vmin
                self.combobox_min.lineEdit().setText(str(self.vmin) if self.vmin is not None else "")
                changed = True
            if vmax != self.vmax:
                # print(( "setting vmax to", vmax))
                self.vmax = vmax
                self.combobox_max.lineEdit().setText(str(self.vmax) if self.vmax is not None else "")
                changed = True
            self.combobox_min.blockSignals(False)
            self.combobox_max.blockSignals(False)
            # self.combobox.setCurrentIndex(options.index(getter()))
            # setter(value)
            if update and changed:
                self.update()
        # auto getter and setter
        setattr(self, "get_value", getter)
        setattr(self, "set_value", wrap_setter)

        def get():
            # vmin, vmax = None, None
            if self.combobox_min.lineEdit().text().strip():
                try:
                    self.vmin = float(self.combobox_min.lineEdit().text())
                except:
                    logger.exception("parsing vmin")
                    dialog_error(self.combobox_min, "Error parsing number", "Cannot parse number: %s" % self.combobox_min.lineEdit().text())

            if self.combobox_max.lineEdit().text().strip():
                try:
                    self.vmax = float(self.combobox_max.lineEdit().text())
                except:
                    logger.exception("parsing vmax")
                    dialog_error(self.combobox_max, "Error parsing number", "Cannot parse number: %s" % self.combobox_max.lineEdit().text())
            return (self.vmin, self.vmax) if self.vmin is not None and self.vmax is not None else None

        def on_change(_ignore=None):
            # setter(self.options[index])
            value = get()
            if value:
                vmin, vmax = value
                if setter((vmin, vmax)):
                    update()
                # self.weight_x_box.lineEdit().editingFinished.connect(lambda _=None: self.onWeightXExpr())

        self.combobox_min.lineEdit().returnPressed.connect(on_change)
        self.combobox_max.lineEdit().returnPressed.connect(on_change)
        # self.combobox_min.currentIndexChanged.connect(on_change)
        # self.combobox_max.currentIndexChanged.connect(on_change)
        self.combobox_layout = QtGui.QHBoxLayout(parent)
        self.combobox_layout.addWidget(self.combobox_min)
        self.combobox_layout.addWidget(self.combobox_max)
        # self.combobox_min.currentIndexChanged.connect(on_change_min)
        # self.combobox_max.currentIndexChanged.connect(on_change_max)
        # self.combobox.setCurrentIndex(options.index(getter()))

        if 1:
            from vaex.ui.icons import iconfile
            self.tool_button = QtGui.QToolButton(parent)
            self.tool_button .setIcon(QtGui.QIcon(iconfile('gear')))
            self.tool_menu = QtGui.QMenu()
            self.tool_button.setMenu(self.tool_menu)
            self.tool_button.setPopupMode(QtGui.QToolButton.InstantPopup)

            def flip(_=None):
                value = get()
                if value:
                    vmin, vmax = value
                    setter((vmax, vmin))
                    self.set_value((vmax, vmin))
            self.action_flip = QtGui.QAction("Flip axis", parent)
            self.action_flip.triggered.connect(flip)
            self.tool_menu.addAction(self.action_flip)

            def copy(_=None):
                value = get()
                if value:
                    clipboard = QtGui.QApplication.clipboard()
                    text = str(value)
                    clipboard.setText(text)
            self.action_copy = QtGui.QAction("Copy", parent)
            self.action_copy.triggered.connect(copy)
            self.tool_menu.addAction(self.action_copy)

            def paste(_=None):
                clipboard = QtGui.QApplication.clipboard()
                text = clipboard.text()
                try:
                    vmin, vmax = eval(text)
                    setter((vmin, vmax))
                    self.set_value((vmin, vmax))
                except Exception as e:
                    dialog_error(parent, "Could not parse min/max values", "Could not parse min/max values: %r" % e)
            self.action_paste = QtGui.QAction("Paste", parent)
            self.action_paste.triggered.connect(paste)
            self.tool_menu.addAction(self.action_paste)

    def add_to_grid_layout(self, row, grid_layout):
        # grid_layout.addLayout(self.combobox_layout, row, 1)
        grid_layout.addWidget(self.label, row, 0)
        grid_layout.addWidget(self.combobox_min, row, 1)
        grid_layout.addWidget(self.tool_button, row, 2)
        row += 1
        # grid_layout.addWidget(self.label, row, 0)
        grid_layout.addWidget(self.combobox_max, row, 1)
        return row + 1


# list of rgb values from tableau20
color_list = [(255, 187, 120),
              (255, 127, 14),
              (174, 199, 232),
              (44, 160, 44),
              (31, 119, 180),
              (255, 152, 150),
              (214, 39, 40),
              (197, 176, 213),
              (152, 223, 138),
              (148, 103, 189),
              (247, 182, 210),
              (227, 119, 194),
              (196, 156, 148),
              (140, 86, 75),
              (127, 127, 127),
              (219, 219, 141),
              (199, 199, 199),
              (188, 189, 34),
              (158, 218, 229),
              (23, 190, 207)]


class ColorOption(object):
    def __init__(self, parent, label, getter, setter, update=lambda: None):
        self.update = update
        # self.options = options
        self.label = QtGui.QLabel(label, parent)
        self.combobox = QtGui.QComboBox(parent)
        index = 0
        self.qt_colors = []
        for color_tuple in color_list:
            # self.combobox.addItems(options)
            self.combobox.addItem(",".join(map(str, color_tuple)))
            model = self.combobox.model().index(index, 0)
            color = QtGui.QColor(*color_tuple)
            self.combobox.model().setData(model, color, QtCore.Qt.BackgroundColorRole)
            index += 1
            self.qt_colors.append(color)

        def wrap_setter(value, update=True):
            index = color_list.index(getter())
            self.combobox.setCurrentIndex(index)
            self.combobox.palette().setColor(QtGui.QPalette.Background, self.qt_colors[index])
            self.combobox.palette().setColor(QtGui.QPalette.Highlight, self.qt_colors[index])
            setter([c / 255. for c in value])
            if update:
                self.update()
        # auto getter and setter
        setattr(self, "get_value", getter)
        setattr(self, "set_value", wrap_setter)

        def on_change(index):
            self.set_value(color_list[index])
            update()
        self.combobox.currentIndexChanged.connect(on_change)
        self.combobox.setCurrentIndex(color_list.index(getter()))

    def add_to_grid_layout(self, row, grid_layout):
        grid_layout.addWidget(self.label, row, 0)
        grid_layout.addWidget(self.combobox, row, 1)
        return row + 1


class Checkbox(object):
    def __init__(self, parent, label_text, getter, setter, update=lambda: None):
        self.update = update
        self.label = QtGui.QLabel(label_text, parent)
        self.checkbox = QtGui.QCheckBox(parent)

        def wrap_setter(value, update=True):
            self.checkbox.setChecked(value)
            setter(value)
            if update:
                self.update()
        # auto getter and setter
        setattr(self, "get_value", getter)
        setattr(self, "set_value", wrap_setter)

        def on_change(state):
            value = state == QtCore.Qt.Checked
            setter(value)
            self.update()
        self.checkbox.setChecked(getter())
        self.checkbox.stateChanged.connect(on_change)

    def add_to_grid_layout(self, row, grid_layout, column_start=0):
        grid_layout.addWidget(self.label, row, column_start + 0)
        grid_layout.addWidget(self.checkbox, row, column_start + 1)
        return row + 1


class Slider(object):
    def __init__(self, parent, label_text, value_min, value_max, value_steps, getter, setter, name=None, format="{0:<0.3f}", transform=lambda x: x, inverse=lambda x: x, update=lambda: None, uselog=False, numeric_type=float):
        if name is None:
            name = label_text
        if uselog:
            def transform(x): return 10**x

            def inverse(x): return math.log10(x)
        # self.properties.append(name)
        self.update = update
        self.label = QtGui.QLabel(label_text, parent)
        self.label_value = QtGui.QLabel(label_text, parent)
        self.slider = QtGui.QSlider(parent)
        self.slider.setOrientation(QtCore.Qt.Horizontal)
        self.slider.setRange(0, value_steps)

        def wrap_setter(value, update=True):
            self.slider.setValue((inverse(value) - inverse(value_min)) / (inverse(value_max) - inverse(value_min)) * value_steps)
            setter(value)
            if update:
                self.update()
        # auto getter and setter
        setattr(self, "get_value", getter)
        setattr(self, "set_value", wrap_setter)

        def update_text():
            # label.setText("mean/sigma: {0:<0.3f}/{1:.3g} opacity: {2:.3g}".format(self.tool.function_means[i], self.tool.function_sigmas[i], self.tool.function_opacities[i]))
            self.label_value.setText(format.format(getter()))

        def on_change(index, slider=self.slider):
            value = numeric_type(index / float(value_steps) * (inverse(value_max) - inverse(value_min)) + inverse(value_min))
            setter(transform(value))
            self.update()
            update_text()
        self.slider.setValue((inverse(getter()) - inverse(value_min)) * value_steps / (inverse(value_max) - inverse(value_min)))
        update_text()
        self.slider.valueChanged.connect(on_change)
        # return label, slider, label_value

    def add_to_grid_layout(self, row, grid_layout):
        grid_layout.addWidget(self.label, row, 0)
        grid_layout.addWidget(self.slider, row, 1)
        grid_layout.addWidget(self.label_value, row, 2)
        return row + 1


class QuickDialog(QtGui.QDialog):
    def __init__(self, parent, title, validate=None):
        QtGui.QDialog.__init__(self, parent)
        self.setWindowTitle(title)
        self.layout = QtGui.QFormLayout(self)
        self.layout.setFieldGrowthPolicy(QtGui.QFormLayout.AllNonFixedFieldsGrow)
        self.values = {}
        self.widgets = {}
        self.validate = validate
        self.button_box = QtGui.QDialogButtonBox(QtGui.QDialogButtonBox.Ok | QtGui.QDialogButtonBox.Cancel, QtCore.Qt.Horizontal, self)
        self.button_box.accepted.connect(self.check_accept)
        self.button_box.rejected.connect(self.reject)
        self.setLayout(self.layout)

    def get(self):
        self.layout.addWidget(self.button_box)
        if self.exec_() == QtGui.QDialog.Accepted:
            return self.values
        else:
            return None

    def check_accept(self):
        logger.debug("on accepted")
        for name, widget in self.widgets.items():
            if isinstance(widget, QtGui.QLabel):
                pass  # self.values[name] = None
            elif isinstance(widget, QtGui.QLineEdit):
                self.values[name] = widget.text()
            elif isinstance(widget, QtGui.QComboBox):
                self.values[name] = widget.currentText()  # lineEdit().text()
            else:
                raise NotImplementedError
        if self.validate is None or self.validate(self, self.values):
            self.accept()

    def accept(self):  # (self, *args):
        return QtGui.QDialog.accept(self)

    def add_label(self, name, label=""):
        self.widgets[name] = widget = QtGui.QLabel(label)
        self.layout.addRow("", widget)

    def add_text(self, name, label="", value="", placeholder=None):
        self.widgets[name] = widget = QtGui.QLineEdit(value, self)
        if placeholder:
            widget.setPlaceholderText(placeholder)
        self.layout.addRow(label, widget)

    def add_password(self, name, label="", value="", placeholder=None):
        self.widgets[name] = widget = QtGui.QLineEdit(value, self)
        widget.setEchoMode(QtGui.QLineEdit.Password)
        if placeholder:
            widget.setPlaceholderText(placeholder)
        self.layout.addRow(label, widget)

    def add_ucd(self, name, label="", value=""):
        self.widgets[name] = widget = QtGui.QLineEdit(value, self)
        widget.setCompleter(vaex.ui.completer.UCDCompleter(widget))
        self.layout.addRow(label, widget)

    def add_combo_edit(self, name, label="", value="", values=[]):
        self.widgets[name] = widget = QtGui.QComboBox(self)
        widget.addItems([value] + values)
        widget.setEditable(True)
        self.layout.addRow(label, widget)

    def add_expression(self, name, label, value, dataset):
        import vaex.ui.completer
        self.widgets[name] = widget = vaex.ui.completer.ExpressionCombobox(self, dataset)
        if value is not None:
            widget.lineEdit().setText(value)
        self.layout.addRow(label, widget)

    def add_variable_expression(self, name, label, value, dataset):
        import vaex.ui.completer
        self.widgets[name] = widget = vaex.ui.completer.ExpressionCombobox(self, dataset, variables=True)
        if value is not None:
            widget.lineEdit().setText(value)
        self.layout.addRow(label, widget)

    def add_combo(self, name, label="", values=[]):
        self.widgets[name] = widget = QtGui.QComboBox(self)
        widget.addItems(values)
        self.layout.addRow(label, widget)


def get_path_save(parent, title="Save file", path="", file_mask="HDF5 *.hdf5"):
    path = QtGui.QFileDialog.getSaveFileName(parent, title, path, file_mask)
    if isinstance(path, tuple):
        path = str(path[0])  # ]
    return str(path)


def get_path_open(parent, title="Select file", path="", file_mask="HDF5 *.hdf5"):
    path = QtGui.QFileDialog.getOpenFileName(parent, title, path, file_mask)
    if isinstance(path, tuple):
        path = str(path[0])  # ]
    return str(path)


def getdir(parent, title, start_directory=""):
    result = QtGui.QFileDialog.getExistingDirectory(parent, title, "", QtGui.QFileDialog.ShowDirsOnly | QtGui.QFileDialog.DontResolveSymlinks)
    return None if result is None else str(result)


def gettext(parent, title, label, default=""):
    text, ok = QtGui.QInputDialog.getText(parent, title, label, QtGui.QLineEdit.Normal, default)
    return str(text) if ok else None


class Thenner(object):
    def __init__(self, callback):
        self.callback = callback
        self.thennable = False

    def then(self, *args, **kwargs):
        self.thennable = True
        self.args = args
        self.kwargs = kwargs

    def do(self):
        if self.thennable:
            self.callback(*self.args, **self.kwargs)


def set_choose(value, ok=None):
    global choose
    thenner = Thenner(set_choose)

    def wrapper(*args):
        thenner.do()
        return value  # if ok is None else (value, ok)
    choose = wrapper
    return thenner


QtGui.QInputDialog_getItem = QtGui.QInputDialog.getItem


def choose(parent, title, label, options, index=0, editable=False):
    text, ok = QtGui.QInputDialog_getItem(parent, title, label, options, index, editable)
    if editable:
        return text if ok else None
    else:
        return options.index(text) if ok else None


def set_select_many(ok, mask):
    global select_many

    def wrapper(*args):
        return ok, mask
    select_many = wrapper


def select_many(parent, title, options):
    dialog = QtGui.QDialog(parent)
    dialog.setWindowTitle(title)
    dialog.setModal(True)
    layout = QtGui.QGridLayout(dialog)
    dialog.setLayout(layout)

    scroll_area = QtGui.QScrollArea(dialog)
    scroll_area.setWidgetResizable(True)
    frame = QtGui.QWidget(scroll_area)
    layout_frame = QtGui.QVBoxLayout(frame)
    # frame.setMinimumSize(100,400)
    frame.setLayout(layout_frame)
    checkboxes = [QtGui.QCheckBox(option, frame) for option in options]
    scroll_area.setWidget(frame)
    row = 0
    for checkbox in checkboxes:
        checkbox.setCheckState(QtCore.Qt.Checked)
        layout_frame.addWidget(checkbox)  # , row, 0)
        row += 1

    buttonLayout = QtGui.QHBoxLayout()
    button_ok = QtGui.QPushButton("Ok", dialog)
    button_cancel = QtGui.QPushButton("Cancel", dialog)
    button_cancel.clicked.connect(dialog.reject)
    button_ok.clicked.connect(dialog.accept)

    buttonLayout.addWidget(button_cancel)
    buttonLayout.addWidget(button_ok)
    layout.addWidget(scroll_area)
    layout.addLayout(buttonLayout, row, 0)
    # options_selected = []
    value = dialog.exec_()
    mask = [checkbox.checkState() == QtCore.Qt.Checked for checkbox in checkboxes]
    return value == QtGui.QDialog.Accepted, mask


def memory_check_ok(parent, bytes_needed):
    bytes_available = psutil.virtual_memory().available
    alot = bytes_needed / bytes_available > 0.5
    required = vaex.utils.filesize_format(bytes_needed)
    available = vaex.utils.filesize_format(bytes_available)
    msg = "This action required {required} of memory, while you have {available}. Are you sure you want to continue?".format(**locals())
    return not alot or dialog_confirm(parent, "A lot of memory requested", msg)


def dialog_error(parent, title, msg):
    QtGui.QMessageBox.warning(parent, title, msg)


def dialog_info(parent, title, msg):
    QtGui.QMessageBox.information(parent, title, msg)


def dialog_confirm(parent, title, msg, to_all=False):
    # return QtGui.QMessageBox.information(parent, title, msg, QtGui.QMessageBox.Yes|QtGui.QMessageBox.No) == QtGui.QMessageBox.Yes
    msgbox = QtGui.QMessageBox(parent)
    msgbox.setText(msg)
    msgbox.setWindowTitle(title)
    # , title, msg, QtGui.QMessageBox.Yes|QtGui.QMessageBox.No) == QtGui.QMessageBox.Yes
    msgbox.addButton(QtGui.QMessageBox.Yes)
    if to_all:
        msgbox.addButton(QtGui.QMessageBox.YesToAll)
        msgbox.setDefaultButton(QtGui.QMessageBox.YesToAll)
    else:
        msgbox.setDefaultButton(QtGui.QMessageBox.Yes)
    msgbox.addButton(QtGui.QMessageBox.No)
    result = msgbox.exec_()
    if to_all:
        return result in [QtGui.QMessageBox.Yes, QtGui.QMessageBox.YesToAll], result == QtGui.QMessageBox.YesToAll
    else:
        return result in [QtGui.QMessageBox.Yes]


confirm = dialog_confirm


def email(text):
    osname = platform.system().lower()
    if osname == "linux":
        text = text.replace("#", "%23")  # for some reason, # needs to be double quoted on linux, otherwise it is interpreted as comment symbol

    body = urlquote(text)

    subject = urlquote('Error report for: ' + vaex.__full_name__)
    mailto = "mailto:maartenbreddels@gmail.com?subject={subject}&body={body}".format(**locals())
    print("open:", mailto)
    vaex.utils.os_open(mailto)


def old_email(text):
    # Open a plain text file for reading.  For this example, assume that
    msg = MIMEText(text)

    msg['Subject'] = 'Error report for: ' + vaex.__full_name__
    email_from = "vaex@astro.rug.nl"
    # email_to = "breddels@astro.rug.nl"
    email_to = "maartenbreddels@gmail.com"
    msg['From'] = email_to
    msg['To'] = email_to

    # Send the message via our own SMTP server, but don't include the
    # envelope header.
    # s = smtplib.SMTP('mailhost.astro.rug.nl')
    s = smtplib.SMTP('smtp.gmail.com')
    s.helo('fw1.astro.rug.nl')
    s.sendmail(email_to, [email_to], msg.as_string())
    s.quit()


def qt_exception(parent, exctype, value, traceback):
    trace_lines = tb.format_exception(exctype, value, traceback)
    trace = "".join(trace_lines)
    print(trace)
    info = "username: %r\n" % (getpass.getuser(),)
    info += "program: %r\n" % vaex.__program_name__
    info += "version: %r\n" % vaex.__version__
    info += "full name: %r\n" % vaex.__full_name__
    info += "arguments: %r\n" % sys.argv
    info += "Qt version: %r\n" % qt_version

    attrs = sorted(dir(platform))
    for attr in attrs:
        if not attr.startswith("_") and attr not in ["popen", "system_alias"]:
            f = getattr(platform, attr)
            if isinstance(f, collections.Callable):
                try:
                    info += "%s: %r\n" % (attr, f())
                except:
                    pass
    # , platform.architecture(), platform.dist(), platform.linux_distribution(),

    report = info + "\n" + trace
    text = """An unexpected error occured, you may press ok and continue, but the program might be unstable.

""" + report

    dialog = QtGui.QMessageBox(parent)
    dialog.setText("Unexpected error: %s\nDo you want to continue" % (exctype, ))
    # dialog.setInformativeText(text)
    dialog.setDetailedText(text)
    buttonSend = QtGui.QPushButton("Email report", dialog)
    buttonQuit = QtGui.QPushButton("Quit program", dialog)
    buttonContinue = QtGui.QPushButton("Continue", dialog)

    def exit(ignore=None):
        print("exit")
        sys.exit(1)

    def _email(ignore=None):
        if QtGui.QMessageBox.information(dialog, "Send report", "Confirm that you want to send a report", QtGui.QMessageBox.Abort | QtGui.QMessageBox.Yes) == QtGui.QMessageBox.Yes:
            email(report)
    buttonQuit.clicked.connect(exit)
    buttonSend.clicked.connect(_email)
    dialog.addButton(buttonSend, QtGui.QMessageBox.YesRole)
    dialog.addButton(buttonQuit, QtGui.QMessageBox.NoRole)
    dialog.addButton(buttonContinue, QtGui.QMessageBox.YesRole)
    dialog.setDefaultButton(buttonSend)
    dialog.setEscapeButton(buttonContinue)
    dialog.raise_()
    dialog.exec_()
    # QtGui.QMessageBox.critical(parent, "Unexpected error: %r" % (value, ), text)
