class OWDatabasesUpdate(OWWidget):

    name = "Databases Update"
    description = "Update local systems biology databases."
    icon = "../widgets/icons/OWDatabasesUpdate.svg"
    priority = 1

    inputs = []
    outputs = []

    want_main_area = False

    def __init__(self,
                 parent=None,
                 signalManager=None,
                 name="Databases update"):
        OWWidget.__init__(self,
                          parent,
                          signalManager,
                          name,
                          wantMainArea=False)

        self.searchString = ""

        fbox = gui.widgetBox(self.controlArea, "Filter")
        self.completer = TokenListCompleter(self,
                                            caseSensitivity=Qt.CaseInsensitive)
        self.lineEditFilter = QLineEdit(textChanged=self.search_update)
        self.lineEditFilter.setCompleter(self.completer)

        fbox.layout().addWidget(self.lineEditFilter)

        box = gui.widgetBox(self.controlArea, "Files")
        self.filesView = QTreeWidget(self)
        self.filesView.setHeaderLabels(header_labels)
        self.filesView.setRootIsDecorated(False)
        self.filesView.setUniformRowHeights(True)
        self.filesView.setSelectionMode(QAbstractItemView.NoSelection)
        self.filesView.setSortingEnabled(True)
        self.filesView.sortItems(header.Title, Qt.AscendingOrder)
        self.filesView.setItemDelegateForColumn(
            0, UpdateOptionsItemDelegate(self.filesView))

        self.filesView.model().layoutChanged.connect(self.search_update)

        box.layout().addWidget(self.filesView)

        layout = QHBoxLayout()
        gui.widgetBox(self.controlArea, margin=0, orientation=layout)

        self.updateButton = gui.button(
            box,
            self,
            "Update all",
            callback=self.update_all,
            tooltip="Update all updatable files",
        )

        self.downloadButton = gui.button(
            box,
            self,
            "Download all",
            callback=self.download_filtered,
            tooltip="Download all filtered files shown")

        self.cancelButton = gui.button(
            box,
            self,
            "Cancel",
            callback=self.cancel_active_threads,
            tooltip="Cancel scheduled downloads/updates.")

        self.addButton = gui.button(box,
                                    self,
                                    "Add ...",
                                    callback=self.__handle_dialog,
                                    tooltip="Add files for personal use.")

        layout.addWidget(self.updateButton)
        layout.addWidget(self.downloadButton)
        layout.addWidget(self.cancelButton)
        layout.addStretch()
        layout.addWidget(self.addButton)

        # Enable retryButton once connection is established
        # self.retryButton = gui.button(
        #     box, self, "Reconnect", callback=self.initialize_files_view
        # )
        # self.retryButton.hide()

        self.resize(800, 600)

        self.update_items = []
        self._dialog = None
        self.progress_bar = None

        # threads
        self.threadpool = QThreadPool(self)
        #self.threadpool.setMaxThreadCount(1)
        self.workers = list()

        self.initialize_files_view()

    def __handle_dialog(self):
        if not self._dialog:
            self._dialog = FileUploadHelper(self)
        self._dialog.show()

    def __progress_advance(self):
        # GUI should be updated in main thread. That's why we are calling advance method here
        if self.progress_bar:
            self.progress_bar.advance()

    def handle_worker_exception(self, ex):
        self.progress_bar.finish()
        self.setStatusMessage('')

        if isinstance(ex, ConnectionError):
            # TODO: set warning messages
            pass

        print(ex)

    def initialize_files_view(self):
        # self.retryButton.hide()

        # clear view
        self.filesView.clear()
        # init progress bar
        self.progress_bar = gui.ProgressBar(self, iterations=3)
        # status message
        self.setStatusMessage('initializing')

        worker = Worker(evaluate_files_state, progress_callback=True)
        worker.signals.progress.connect(self.__progress_advance)
        worker.signals.result.connect(self.set_files_list)
        worker.signals.error.connect(self.handle_worker_exception)

        # move download process to worker thread
        self.threadpool.start(worker)
        self.setEnabled(False)

    def __create_action_button(self, fs, retry=None):
        if not fs.state not in [OUTDATED, USER_FILE] or not retry:
            self.filesView.setItemWidget(fs.tree_item, header.Update, None)

        button = QToolButton(None)
        if not retry:
            if fs.state == OUTDATED:
                button.setText('Update')
                button.clicked.connect(
                    partial(self.submit_download_task, fs.domain, fs.filename,
                            True))
            elif fs.state == USER_FILE:
                if not fs.info_server:
                    button.setText('Remove')
                    button.clicked.connect(
                        partial(self.submit_remove_task, fs.domain,
                                fs.filename))
                else:
                    button.setText('Use server version')
                    button.clicked.connect(
                        partial(self.submit_download_task, fs.domain,
                                fs.filename, True))
        else:
            button.setText('Retry')
            button.clicked.connect(
                partial(self.submit_download_task, fs.domain, fs.filename,
                        True))

        button.setMaximumWidth(120)
        button.setMaximumHeight(20)
        button.setMinimumHeight(20)

        if sys.platform == "darwin":
            button.setAttribute(Qt.WA_MacSmallSize)

        self.filesView.setItemWidget(fs.tree_item, header.Update, button)

    def set_files_list(self, result):
        """ Set the files to show.
        """
        assert threading.current_thread() == threading.main_thread()
        self.progress_bar.finish()
        self.setStatusMessage('')
        self.setEnabled(True)

        self.update_items = result
        all_tags = set()

        for fs in self.update_items:
            fs.tree_item = FileStateItem(fs)
            fs.download_option = DownloadOption(state=fs.state)

            fs.download_option.download_clicked.connect(
                partial(self.submit_download_task, fs.domain, fs.filename))
            fs.download_option.remove_clicked.connect(
                partial(self.submit_remove_task, fs.domain, fs.filename))

        # add widget items to the QTreeWidget
        self.filesView.addTopLevelItems(
            [fs.tree_item for fs in self.update_items])

        # add action widgets to tree items
        for fs in self.update_items:
            self.filesView.setItemWidget(fs.tree_item, header.Download,
                                         fs.download_option)
            if fs.state in [USER_FILE, OUTDATED]:
                self.__create_action_button(fs)

            all_tags.update(fs.tags)

        self.filesView.setColumnWidth(
            header.Download, self.filesView.sizeHintForColumn(header.Download))

        for column in range(1, len(header_labels)):
            self.filesView.resizeColumnToContents(column)

        hints = [hint for hint in sorted(all_tags) if not hint.startswith("#")]
        self.completer.setTokenList(hints)
        self.search_update()
        self.toggle_action_buttons()
        self.cancelButton.setEnabled(False)

    def toggle_action_buttons(self):
        selected_items = [
            fs for fs in self.update_items if not fs.tree_item.isHidden()
        ]

        def button_check(sel_items, state, button):
            for item in sel_items:
                if item.state != state:
                    button.setEnabled(False)
                else:
                    button.setEnabled(True)
                    break

        button_check(selected_items, OUTDATED, self.updateButton)
        button_check(selected_items, AVAILABLE, self.downloadButton)

    def search_update(self, searchString=None):
        strings = str(self.lineEditFilter.text()).split()
        for fs in self.update_items:
            hide = not all(UpdateItem_match(fs, string) for string in strings)
            fs.tree_item.setHidden(hide)
        self.toggle_action_buttons()

    def update_all(self):
        for fs in self.update_items:
            if fs.state == OUTDATED and not fs.tree_item.isHidden():
                self.submit_download_task(fs.domain, fs.filename)

    def download_filtered(self):
        for fs in self.update_items:
            if not fs.tree_item.isHidden() and fs.state in [
                    AVAILABLE, OUTDATED
            ]:
                self.submit_download_task(fs.domain, fs.filename, start=False)

        self.run_download_tasks()

    def submit_download_task(self, domain, filename, start=True):
        """ Submit the (domain, filename) to be downloaded/updated.
        """
        # get selected tree item
        index = self.tree_item_index(domain, filename)
        fs = self.update_items[index]

        worker = Worker(download_server_file,
                        fs,
                        index,
                        progress_callback=True)
        worker.signals.progress.connect(self.__progress_advance)
        worker.signals.result.connect(self.on_download_finished)
        worker.signals.error.connect(self.on_download_exception)

        self.workers.append(worker)

        if start:
            self.run_download_tasks()

    def run_download_tasks(self):
        self.cancelButton.setEnabled(True)
        # init progress bar

        self.progress_bar = gui.ProgressBar(self,
                                            iterations=len(self.workers) * 100)

        # status message
        self.setStatusMessage('downloading')

        # move workers to threadpool
        [self.threadpool.start(worker) for worker in self.workers]
        self.filesView.setDisabled(True)
        # reset list of workers
        self.workers = list()

    def on_download_exception(self, ex):
        assert threading.current_thread() == threading.main_thread()
        self.progress_bar.finish()
        self.setStatusMessage('')
        print(ex)
        if isinstance(ex, ValueError):
            fs, index = ex.args

            # restore state and retry
            fs.refresh_state()
            fs.tree_item.update_data(fs)
            fs.download_option.state = fs.state
            self.__create_action_button(fs, retry=True)

    def on_download_finished(self, result):
        assert threading.current_thread() == threading.main_thread()

        # We check if all workers have completed. If not, continue
        if self.progress_bar.count == 100 or self.threadpool.activeThreadCount(
        ) == 0:
            self.filesView.setDisabled(False)
            self.progress_bar.finish()
            self.setStatusMessage('')

        fs, index = result
        # re-evaluate File State
        info = serverfiles.info(fs.domain, fs.filename)
        fs.refresh_state(info_local=info, info_server=info)
        # reinitialize treeWidgetItem
        fs.tree_item.update_data(fs)
        # reinitialize OptionWidget
        fs.download_option.state = fs.state
        self.filesView.setItemWidget(fs.tree_item, header.Update, None)

        self.toggle_action_buttons()
        for column in range(1, len(header_labels)):
            self.filesView.resizeColumnToContents(column)

    def submit_remove_task(self, domain, filename):
        serverfiles.LOCALFILES.remove(domain, filename)

        index = self.tree_item_index(domain, filename)
        fs = self.update_items[index]

        if fs.state == USER_FILE:
            self.filesView.takeTopLevelItem(
                self.filesView.indexOfTopLevelItem(fs.tree_item))
            self.update_items.remove(fs)
            # self.filesView.removeItemWidget(index)
        else:
            # refresh item state
            fs.info_local = None
            fs.refresh_state()
            # reinitialize treeWidgetItem
            fs.tree_item.update_data(fs)
            # reinitialize OptionWidget
            fs.download_option.state = fs.state

        self.toggle_action_buttons()

    def cancel_active_threads(self):
        """ Cancel all pending update/download tasks (that have not yet started).
        """
        if self.threadpool:
            self.threadpool.clear()

    def tree_item_index(self, domain, filename):
        for i, fs in enumerate(self.update_items):
            if fs.domain == domain and fs.filename == filename:
                return i
        raise ValueError("%r, %r not in update list" % (domain, filename))

    def onDeleteWidget(self):
        self.cancel_active_threads()
        OWWidget.onDeleteWidget(self)