"""
classes used in progress pages
from first run wizard
"""
try:
    from collections import OrderedDict
except ImportError:  # pragma: no cover
    # We must be in 2.6
    from leap.util.dicts import OrderedDict

import logging

from PyQt4 import QtCore
from PyQt4 import QtGui

from leap.gui.threads import FunThread

from leap.gui import mainwindow_rc

ICON_CHECKMARK = ":/images/Dialog-accept.png"
ICON_FAILED = ":/images/Dialog-error.png"
ICON_WAITING = ":/images/Emblem-question.png"

logger = logging.getLogger(__name__)


class ImgWidget(QtGui.QWidget):

    # XXX move to widgets

    def __init__(self, parent=None, img=None):
        super(ImgWidget, self).__init__(parent)
        self.pic = QtGui.QPixmap(img)

    def paintEvent(self, event):
        painter = QtGui.QPainter(self)
        painter.drawPixmap(0, 0, self.pic)


class ProgressStep(object):
    """
    Data model for sequential steps
    to be used in a progress page in
    connection wizard
    """
    NAME = 0
    DONE = 1

    def __init__(self, stepname, done, index=None):
        """
        @param step: the name of  the step
        @type step: str
        @param done: whether is completed or not
        @type done: bool
        """
        self.index = int(index) if index else 0
        self.name = unicode(stepname)
        self.done = bool(done)

    @classmethod
    def columns(self):
        return ('name', 'done')


class ProgressStepContainer(object):
    """
    a container for ProgressSteps objects
    access data in the internal dict
    """

    def __init__(self):
        self.dirty = False
        self.steps = {}

    def step(self, identity):
        return self.steps.get(identity, None)

    def addStep(self, step):
        self.steps[step.index] = step

    def removeStep(self, step):
        if step and self.steps.get(step.index, None):
            del self.steps[step.index]
            del step
            self.dirty = True

    def removeAllSteps(self):
        for item in iter(self):
            self.removeStep(item)

    @property
    def columns(self):
        return ProgressStep.columns()

    def __len__(self):
        return len(self.steps)

    def __iter__(self):
        for step in self.steps.values():
            yield step


class StepsTableWidget(QtGui.QTableWidget):
    """
    initializes a TableWidget
    suitable for our display purposes, like removing
    header info and grid display
    """

    def __init__(self, parent=None):
        super(StepsTableWidget, self).__init__(parent=parent)

        # remove headers and all edit/select behavior
        self.horizontalHeader().hide()
        self.verticalHeader().hide()
        self.setEditTriggers(
            QtGui.QAbstractItemView.NoEditTriggers)
        self.setSelectionMode(
            QtGui.QAbstractItemView.NoSelection)
        width = self.width()

        # WTF? Here init width is 100...
        # but on populating is 456... :(
        #logger.debug('init table. width=%s' % width)

        # XXX do we need this initial?
        self.horizontalHeader().resizeSection(0, width * 0.7)

        # this disables the table grid.
        # we should add alignment to the ImgWidget (it's top-left now)
        self.setShowGrid(False)
        self.setFocusPolicy(QtCore.Qt.NoFocus)
        #self.setStyleSheet("QTableView{outline: 0;}")

        # XXX change image for done to rc

        # Note about the "done" status painting:
        #
        # XXX currently we are setting the CellWidget
        # for the whole table on a per-row basis
        # (on add_status_line method on ValidationPage).
        # However, a more generic solution might be
        # to implement a custom Delegate that overwrites
        # the paint method (so it paints a checked tickmark if
        # done is True and some other thing if checking or false).
        # What we have now is quick and works because
        # I'm supposing that on first fail we will
        # go back to previous wizard page to signal the failure.
        # A more generic solution could be used for
        # some failing tests if they are not critical.


class WithStepsMixIn(object):
    """
    This Class is a mixin that can be inherited
    by InlineValidation pages (which will display
    a progress steps widget in the same page as the form)
    or by Validation Pages (which will only display
    the progress steps in the page, below a progress bar widget)
    """
    STEPS_TIMER_MS = 100

    #
    # methods related to worker threads
    # launched for individual checks
    #

    def setupStepsProcessingQueue(self):
        """
        should be called from the init method
        of the derived classes
        """
        self.steps_queue = Queue.Queue()
        self.stepscheck_timer = QtCore.QTimer()
        self.stepscheck_timer.timeout.connect(self.processStepsQueue)
        self.stepscheck_timer.start(self.STEPS_TIMER_MS)
        # we need to keep a reference to child threads
        self.threads = []

    def do_checks(self):
        """
        main entry point for checks.
        it calls _do_checks in derived classes,
        and it expects it to be a generator
        yielding a tuple in the form (("message", progress_int), checkfunction)
        """

        # yo dawg, I heard you like checks
        # so I put a __do_checks in your do_checks
        # for calling others' _do_checks

        def __do_checks(fun=None, queue=None):

            for checkcase in fun():  # pragma: no cover
                checkmsg, checkfun = checkcase

                queue.put(checkmsg)
                if checkfun() is False:
                    queue.put("failed")
                    break

        t = FunThread(fun=partial(
            __do_checks,
            fun=self._do_checks,
            queue=self.steps_queue))
        if hasattr(self, 'on_checks_validation_ready'):
            t.finished.connect(self.on_checks_validation_ready)
        t.begin()
        self.threads.append(t)

    def processStepsQueue(self):
        """
        consume steps queue
        and pass messages
        to the ui updater functions
        """
        while self.steps_queue.qsize():
            try:
                status = self.steps_queue.get(0)
                if status == "failed":
                    self.set_failed_icon()
                else:
                    self.onStepStatusChanged(*status)
            except Queue.Empty:  # pragma: no cover
                pass

    def fail(self, err=None):
        """
        return failed state
        and send error notification as
        a nice side effect. this function is called from
        the _do_checks check functions returned in the
        generator.
        """
        wizard = self.wizard()
        senderr = lambda err: wizard.set_validation_error(
            self.current_page, err)
        self.set_undone()
        if err:
            senderr(err)
        return False

    @QtCore.pyqtSlot()
    def launch_checks(self):
        self.do_checks()

    # (gui) presentation stuff begins #####################

    # slot
    #@QtCore.pyqtSlot(str, int)
    def onStepStatusChanged(self, status, progress=None):
        status = unicode(status)
        if status not in ("head_sentinel", "end_sentinel"):
            self.add_status_line(status)
        if status in ("end_sentinel"):
            #self.checks_finished = True
            self.set_checked_icon()
        if progress and hasattr(self, 'progress'):
            self.progress.setValue(progress)
            self.progress.update()

    def setupSteps(self):
        self.steps = ProgressStepContainer()
        # steps table widget
        if isinstance(self, QtCore.QObject):
            parent = self
        else:
            parent = None
        self.stepsTableWidget = StepsTableWidget(parent=parent)
        zeros = (0, 0, 0, 0)
        self.stepsTableWidget.setContentsMargins(*zeros)
        self.errors = OrderedDict()

    def set_error(self, name, error):
        self.errors[name] = error

    def pop_first_error(self):
        errkey, errval = list(reversed(self.errors.items())).pop()
        del self.errors[errkey]
        return errkey, errval

    def clean_errors(self):
        self.errors = OrderedDict()

    def clean_wizard_errors(self, pagename=None):
        if pagename is None:  # pragma: no cover
            pagename = getattr(self, 'prev_page', None)
        if pagename is None:  # pragma: no cover
            return
        #logger.debug('cleaning wizard errors for %s' % pagename)
        self.wizard().set_validation_error(pagename, None)

    def populateStepsTable(self):
        # from examples,
        # but I guess it's not needed to re-populate
        # the whole table.
        table = self.stepsTableWidget
        table.setRowCount(len(self.steps))
        columns = self.steps.columns
        table.setColumnCount(len(columns))

        for row, step in enumerate(self.steps):
            item = QtGui.QTableWidgetItem(step.name)
            item.setData(QtCore.Qt.UserRole,
                         long(id(step)))
            table.setItem(row, columns.index('name'), item)
            table.setItem(row, columns.index('done'),
                          QtGui.QTableWidgetItem(step.done))
        self.resizeTable()
        self.update()

    def clearTable(self):
        # ??? -- not sure what's the difference
        #self.stepsTableWidget.clear()
        self.stepsTableWidget.clearContents()

    def resizeTable(self):
        # resize first column to ~80%
        table = self.stepsTableWidget
        FIRST_COLUMN_PERCENT = 0.70
        width = table.width()
        #logger.debug('populate table. width=%s' % width)
        table.horizontalHeader().resizeSection(0, width * FIRST_COLUMN_PERCENT)

    def set_item_icon(self, img=ICON_CHECKMARK, current=True):
        """
        mark the last item
        as done
        """
        # setting cell widget.
        # see note on StepsTableWidget about plans to
        # change this for a better solution.
        if not hasattr(self, 'steps'):
            return
        index = len(self.steps)
        table = self.stepsTableWidget
        _index = index - 1 if current else index - 2
        table.setCellWidget(
            _index,
            ProgressStep.DONE,
            ImgWidget(img=img))
        table.update()

    def set_failed_icon(self):
        self.set_item_icon(img=ICON_FAILED, current=True)

    def set_checking_icon(self):
        self.set_item_icon(img=ICON_WAITING, current=True)

    def set_checked_icon(self, current=True):
        self.set_item_icon(current=current)

    def add_status_line(self, message):
        """
        adds a new status line
        and mark the next-to-last item
        as done
        """
        index = len(self.steps)
        step = ProgressStep(message, False, index=index)
        self.steps.addStep(step)
        self.populateStepsTable()
        self.set_checking_icon()
        self.set_checked_icon(current=False)

    # Sets/unsets done flag
    # for isComplete checks

    def set_done(self):
        self.done = True
        self.completeChanged.emit()

    def set_undone(self):
        self.done = False
        self.completeChanged.emit()

    def is_done(self):
        return self.done

    # convenience for going back and forth
    # in the wizard pages.

    def go_back(self):
        self.wizard().back()

    def go_next(self):
        self.wizard().next()


"""
We will use one base class for the intermediate pages
and another one for the in-page validations, both sharing the creation
of the tablewidgets.
The logic of this split comes from where I was trying to solve
the ui update using signals, but now that it's working well with
queues I could join them again.
"""

import Queue
from functools import partial


class InlineValidationPage(QtGui.QWizardPage, WithStepsMixIn):

    def __init__(self, parent=None):
        super(InlineValidationPage, self).__init__(parent)
        self.setupStepsProcessingQueue()
        self.done = False

    # slot

    @QtCore.pyqtSlot()
    def showStepsFrame(self):
        self.valFrame.show()
        self.update()

    # progress frame

    def setupValidationFrame(self):
        qframe = QtGui.QFrame
        valFrame = qframe()
        valFrame.setFrameStyle(qframe.NoFrame)
        valframeLayout = QtGui.QVBoxLayout()
        zeros = (0, 0, 0, 0)
        valframeLayout.setContentsMargins(*zeros)

        valframeLayout.addWidget(self.stepsTableWidget)
        valFrame.setLayout(valframeLayout)
        self.valFrame = valFrame


class ValidationPage(QtGui.QWizardPage, WithStepsMixIn):
    """
    class to be used as an intermediate
    between two pages in a wizard.
    shows feedback to the user and goes back if errors,
    goes forward if ok.
    initializePage triggers a one shot timer
    that calls do_checks.
    Derived classes should implement
    _do_checks and
    _do_validation
    """

    # signals
    stepChanged = QtCore.pyqtSignal([str, int])

    def __init__(self, parent=None):
        super(ValidationPage, self).__init__(parent)
        self.setupSteps()
        #self.connect_step_status()

        layout = QtGui.QVBoxLayout()
        self.progress = QtGui.QProgressBar(self)
        layout.addWidget(self.progress)
        layout.addWidget(self.stepsTableWidget)

        self.setLayout(layout)
        self.layout = layout

        self.timer = QtCore.QTimer()
        self.done = False

        self.setupStepsProcessingQueue()

    def isComplete(self):
        return self.is_done()

    ########################

    def show_progress(self):
        self.progress.show()
        self.stepsTableWidget.show()

    def hide_progress(self):
        self.progress.hide()
        self.stepsTableWidget.hide()

    # pagewizard methods.
    # if overriden, child classes should call super.

    def initializePage(self):
        self.clean_errors()
        self.clean_wizard_errors()
        self.steps.removeAllSteps()
        self.clearTable()
        self.resizeTable()
        self.timer.singleShot(0, self.do_checks)