class OWdictyExpress(OWWidget):

    name = "dictyExpress"
    description = "Time-course gene expression data"
    icon = "../widgets/icons/OWdictyExpress.png"
    want_main_area = True
    priority = 3

    class Inputs:
        pass

    class Outputs:
        etc_data = Output("Data", Table)

    class Error(OWWidget.Error):
        unreachable_host = Msg('Host not reachable')
        invalid_credentials = Msg('Invalid credentials')

    username = settings.Setting('')
    # password = settings.Setting('')
    gene_as_attr_name = settings.Setting(0)

    selected_item = settings.Setting(None, schema_only=True)
    auto_commit = settings.Setting(False, schema_only=True)

    def __init__(self):
        super().__init__()

        self.res = None
        self.organism = '44689'
        self.server = 'https://dictyexpress.research.bcm.edu'
        self.headerLabels = [x[1] for x in Labels]
        self.searchString = ""
        self.items = []

        self.progress_bar = None
        # threads
        self.threadpool = QThreadPool()

        # Login Section
        box = gui.widgetBox(self.controlArea, 'Login')

        self.namefield = gui.lineEdit(box,
                                      self,
                                      "username",
                                      "Username:"******"password",
                                      "Password:"******"Output", addSpace=True)
        gui.radioButtonsInBox(box,
                              self,
                              "gene_as_attr_name",
                              ["Genes in rows", "Genes in columns"],
                              callback=self.invalidate)

        self.controlArea.layout().addWidget(h_line())

        self.refresh_button = gui.button(self.controlArea,
                                         self,
                                         "Refresh",
                                         callback=self.refresh)
        self.handle_cache_button(True)

        gui.rubber(self.controlArea)

        self.commit_button = gui.auto_commit(self.controlArea,
                                             self,
                                             "auto_commit",
                                             "&Commit",
                                             box=False)

        # Experiment Section

        label = QLabel("Available projects:")
        my_font = QFont()
        my_font.setBold(True)
        label.setFont(my_font)
        self.mainArea.layout().addWidget(label)

        self.mainArea.layout().addWidget(h_line())

        self.filter = gui.lineEdit(self.mainArea,
                                   self,
                                   "searchString",
                                   "Filter:",
                                   callbackOnType=True,
                                   callback=self.search_update)

        self.experimentsWidget = QTreeWidget(alternatingRowColors=True,
                                             rootIsDecorated=False,
                                             uniformRowHeights=True,
                                             sortingEnabled=True)

        self.experimentsWidget.setItemDelegateForColumn(
            0, gui.IndicatorItemDelegate(self, role=Qt.DisplayRole))

        self.experimentsWidget.selectionModel().selectionChanged.connect(
            self.on_selection_changed)

        self.experimentsWidget.setHeaderLabels(self.headerLabels)
        self.mainArea.layout().addWidget(self.experimentsWidget)

        self.auth_set()
        self.connect()
        self.sizeHint()

    def sizeHint(self):
        return QSize(1400, 680)

    def auth_set(self):
        self.passfield.setDisabled(not self.username)

    def auth_changed(self):
        self.auth_set()
        self.connect()

    def refresh(self):
        self.reset()
        self.load_experiments()

    def reset(self):
        self.experimentsWidget.clear()  # clear QTreeWidget
        self.items = []
        # self.lastSelected = None
        self.searchString = ""

    def search_update(self):
        parts = self.searchString.split()
        for item in self.items:
            item.setHidden(not all(s in item for s in parts))

    def progress_advance(self):
        # GUI should be updated in main thread. That's why we are calling advance method here
        assert threading.current_thread() == threading.main_thread()
        if self.progress_bar:
            self.progress_bar.advance()

    def handle_error(self, ex):
        self.progress_bar.finish()
        self.setStatusMessage('')
        if isinstance(ex, ConnectionError) or isinstance(ex, ValueError):
            self.Error.unreachable_host()

        print(ex)

    def load_experiments_result(self, experiments):
        self.load_tree_items(experiments)
        self.progress_bar.finish()
        self.setStatusMessage('')

    def connect(self):
        self.res = None
        self.Error.clear()
        self.reset()
        self.handle_cache_button(False)

        user, password = resolwe.DEFAULT_EMAIL, resolwe.DEFAULT_PASSWD
        if self.username or self.password:
            user, password = self.username, self.password

        try:
            self.res = resolwe.connect(user, password, self.server, 'genesis')
        except resolwe.ResolweAuthException:
            self.Error.invalid_credentials()
        else:
            self.load_experiments()
            self.handle_cache_button(True)

    def load_experiments(self):
        if self.res:
            # init progress bar
            self.progress_bar = gui.ProgressBar(self, iterations=2)
            # status message
            self.setStatusMessage('downloading experiments')

            worker = Worker(self.res.fetch_etc_objects, progress_callback=True)
            worker.signals.progress.connect(self.progress_advance)
            worker.signals.result.connect(self.load_experiments_result)
            worker.signals.error.connect(self.handle_error)

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

    def load_tree_items(self, list_of_exp):
        self.items = [
            CustomTreeItem(self.experimentsWidget, item)
            for item in list_of_exp
        ]

        for i in range(len(self.headerLabels)):
            self.experimentsWidget.resizeColumnToContents(i)

        self.set_cached_indicator()
        self.set_selected()

    def set_selected(self):
        for item in self.items:
            if self.selected_item and item.gen_data_id == self.selected_item:
                self.experimentsWidget.setCurrentItem(item)

    def on_selection_changed(self):
        self.invalidate()

    def invalidate(self):
        self.commit()

    def handle_cache_button(self, handle):
        self.refresh_button.setEnabled(handle)

    def send_to_output(self, result):
        self.progress_bar.finish()
        self.setStatusMessage('')

        etc_json, table_name = result

        # convert to table
        data = etc_to_table(etc_json, bool(self.gene_as_attr_name))
        # set table name
        data.name = table_name

        # match genes
        gene_matcher = GeneMatcher(str(self.organism))

        if not bool(self.gene_as_attr_name):
            if 'Gene' in data.domain:
                gene_column = data.domain['Gene']
                gene_names = data.get_column_view(gene_column)[0]
                gene_matcher.genes = gene_names

                domain_ids = Domain([], metas=[StringVariable(ENTREZ_ID)])
                data_ids = [[str(gene.gene_id) if gene.gene_id else '?']
                            for gene in gene_matcher.genes]
                table_ids = Table(domain_ids, data_ids)
                data = Table.concatenate([data, table_ids])

            data.attributes[GENE_ID_COLUMN] = ENTREZ_ID
        else:
            gene_matcher.match_table_attributes(data)
            data.attributes[GENE_ID_ATTRIBUTE] = ENTREZ_ID

        # add table attributes
        data.attributes[TAX_ID] = str(self.organism)
        data.attributes[GENE_AS_ATTRIBUTE_NAME] = bool(self.gene_as_attr_name)

        # reset cache indicators
        self.set_cached_indicator()
        # send data to the output signal
        self.Outputs.etc_data.send(data)

    def commit(self):
        self.Error.clear()
        selected_item = self.experimentsWidget.currentItem(
        )  # get selected TreeItem
        self.selected_item = selected_item.gen_data_id

        if selected_item:
            # init progress bar
            self.progress_bar = gui.ProgressBar(self, iterations=1)
            # status message
            self.setStatusMessage('downloading experiment data')

            worker = Worker(
                self.res.download_etc_data,
                selected_item.gen_data_id,
                table_name=selected_item.data_name,
                progress_callback=True,
            )

            worker.signals.progress.connect(self.progress_advance)
            worker.signals.result.connect(self.send_to_output)
            worker.signals.error.connect(self.handle_error)

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

    def set_cached_indicator(self):
        cached = self.res.get_cached_ids()

        for item in self.items:

            if item.gen_data_id in cached:
                item.setData(0, Qt.DisplayRole, " ")
            else:
                item.setData(0, Qt.DisplayRole, "")
class OWdictyExpress(OWWidget, ConcurrentWidgetMixin):

    name = "dictyExpress"
    description = "Time-course gene expression data"
    icon = "../widgets/icons/OWdictyExpress.svg"
    want_main_area = True
    priority = 20

    class Inputs:
        pass

    class Outputs:
        etc_data = Output("Data", Table)

    class Error(OWWidget.Error):
        unreachable_host = Msg('Host not reachable')
        invalid_credentials = Msg('Invalid credentials')

    gene_as_attr_name = settings.Setting(0)

    selected_item = settings.Setting(None, schema_only=True)
    auto_commit = settings.Setting(False, schema_only=True)

    def __init__(self):
        super().__init__()
        ConcurrentWidgetMixin.__init__(self)

        self._res: Optional[genapi.GenAPI] = None
        self.organism = '44689'
        self.server = 'https://dictyexpress.research.bcm.edu'
        self.headerLabels = [x[1] for x in Labels]
        self.searchString = ""
        self.items = []
        self.genapi_pub_auth = {
            'url': genapi.DEFAULT_URL,
            'username': genapi.DEFAULT_EMAIL,
            'password': genapi.DEFAULT_PASSWD,
        }

        # Login Section
        box = gui.widgetBox(self.controlArea, 'Sign in')
        self.user_info = gui.label(box, self, '')
        self.server_info = gui.label(box, self, '')

        box = gui.widgetBox(box, orientation=Qt.Horizontal)
        self.sign_in_btn = gui.button(box,
                                      self,
                                      'Sign In',
                                      callback=self.sign_in,
                                      autoDefault=False)
        self.sign_out_btn = gui.button(box,
                                       self,
                                       'Sign Out',
                                       callback=self.sign_out,
                                       autoDefault=False)

        box = gui.widgetBox(self.controlArea, "Output")
        gui.radioButtonsInBox(box,
                              self,
                              "gene_as_attr_name",
                              ["Genes in rows", "Genes in columns"],
                              callback=self.invalidate)

        self.clear_cache_btn = gui.button(self.controlArea,
                                          self,
                                          "Clear cache",
                                          autoDefault=False,
                                          callback=self.clear_cache)

        gui.rubber(self.controlArea)

        self.commit_button = gui.auto_commit(self.controlArea,
                                             self,
                                             "auto_commit",
                                             "&Commit",
                                             box=False)

        # Experiment Section

        label = QLabel("Available projects:")
        my_font = QFont()
        my_font.setBold(True)
        label.setFont(my_font)
        self.mainArea.layout().addWidget(label)

        self.filter = gui.lineEdit(self.mainArea,
                                   self,
                                   "searchString",
                                   "Filter:",
                                   callbackOnType=True,
                                   callback=self.search_update)

        self.experimentsWidget = QTreeWidget(alternatingRowColors=True,
                                             rootIsDecorated=False,
                                             uniformRowHeights=True,
                                             sortingEnabled=True)

        self.experimentsWidget.setItemDelegateForColumn(
            0, gui.IndicatorItemDelegate(self, role=Qt.DisplayRole))

        self.experimentsWidget.selectionModel().selectionChanged.connect(
            self.on_selection_changed)

        self.experimentsWidget.setHeaderLabels(self.headerLabels)
        self.mainArea.layout().addWidget(self.experimentsWidget)

        self.sign_in(silent=True)
        self.sizeHint()

    def sizeHint(self):
        return QSize(1400, 680)

    @property
    def res(self):
        return self._res

    @res.setter
    def res(self, value: genapi.GenAPI):
        if isinstance(value, genapi.GenAPI):
            self._res = value
            self.Error.clear()
            self.reset()
            self.load_experiments()
            self.update_user_status()
            self.Outputs.etc_data.send(None)

    def sign_in(self, silent=False):
        dialog = SignIn(self, server_type='genesis')

        if silent:
            dialog.sign_in()
            if dialog.resolwe_instance is not None:
                self.res = dialog.resolwe_instance
            else:
                self.res = connect(**self.genapi_pub_auth,
                                   server_type=resolwe.GENESIS_PLATFORM)

        if not silent and dialog.exec_():
            self.res = dialog.resolwe_instance

    def sign_out(self):
        # Remove username and password
        cm = get_credential_manager(resolwe.GENESIS_PLATFORM)
        del cm.username
        del cm.password
        # Use public credentials when user signs out
        self.res = connect(**self.genapi_pub_auth,
                           server_type=resolwe.GENESIS_PLATFORM)

    def update_user_status(self):
        cm = get_credential_manager(resolwe.GENESIS_PLATFORM)

        if cm.username:
            user_info = f"User: {cm.username}"
            self.sign_in_btn.setEnabled(False)
            self.sign_out_btn.setEnabled(True)
        else:
            user_info = 'User: Anonymous'
            self.sign_in_btn.setEnabled(True)
            self.sign_out_btn.setEnabled(False)

        self.user_info.setText(user_info)
        self.server_info.setText(f'Server: {self.res._gen.url[8:]}')

    def clear_cache(self):
        resolwe.GenAPI.clear_cache()
        self.reset()
        self.load_experiments()

    def reset(self):
        self.experimentsWidget.clear()  # clear QTreeWidget
        self.items = []
        # self.lastSelected = None
        self.searchString = ""

    def search_update(self):
        parts = self.searchString.split()
        for item in self.items:
            item.setHidden(not all(s in item for s in parts))

    def on_exception(self, ex):
        if isinstance(ex, ConnectionError) or isinstance(ex, ValueError):
            self.Error.unreachable_host()

        print(ex)

    def on_done(self, results):
        if isinstance(results, list):
            self.load_tree_items(results)
        elif isinstance(results, tuple):
            self.send_to_output(results)

    def load_experiments(self):
        if self.res:
            self.start(self.res.fetch_etc_objects)

    def load_tree_items(self, list_of_exp):
        self.items = [
            CustomTreeItem(self.experimentsWidget, item)
            for item in list_of_exp
        ]

        for i in range(len(self.headerLabels)):
            self.experimentsWidget.resizeColumnToContents(i)

        self.set_cached_indicator()
        self.set_selected()

    def set_selected(self):
        for item in self.items:
            if self.selected_item and item.gen_data_id == self.selected_item:
                self.experimentsWidget.setCurrentItem(item)

    def on_selection_changed(self):
        self.invalidate()

    def invalidate(self):
        self.commit()

    def send_to_output(self, result):
        etc_json, table_name = result

        # convert to table
        data = etc_to_table(etc_json, bool(self.gene_as_attr_name))
        # set table name
        data.name = table_name

        # match genes
        gene_matcher = GeneMatcher(str(self.organism))

        if not bool(self.gene_as_attr_name):
            if 'Gene' in data.domain:
                data = gene_matcher.match_table_column(
                    data, 'Gene', StringVariable(ENTREZ_ID))
            data.attributes[GENE_ID_COLUMN] = ENTREZ_ID
        else:
            data = gene_matcher.match_table_attributes(data)
            data.attributes[GENE_ID_ATTRIBUTE] = ENTREZ_ID

        # add table attributes
        data.attributes[TAX_ID] = str(self.organism)
        data.attributes[GENE_AS_ATTRIBUTE_NAME] = bool(self.gene_as_attr_name)

        # reset cache indicators
        self.set_cached_indicator()
        # send data to the output signal
        self.Outputs.etc_data.send(data)

    def commit(self):
        self.Error.clear()
        selected_items = self.experimentsWidget.selectedItems(
        )  # get selected TreeItem

        if len(selected_items) < 1:
            self.Outputs.etc_data.send(None)
            return

        selected_item = selected_items[0]
        self.selected_item = selected_item.gen_data_id
        self.start(self.res.download_etc_data,
                   selected_item.gen_data_id,
                   table_name=selected_item.data_name)

    def set_cached_indicator(self):
        cached = self.res.get_cached_ids()

        for item in self.items:

            if item.gen_data_id in cached:
                item.setData(0, Qt.DisplayRole, " ")
            else:
                item.setData(0, Qt.DisplayRole, "")