import logging
import os
import traceback

from qtpy import uic
from qtpy.QtCore import QObject, Signal, Slot, QRunnable, QVariant, Qt
from qtpy.QtGui import QPixmap, QStandardItem, QStandardItemModel
from qtpy.QtWidgets import QDialog, QListWidgetItem, QTreeWidgetItem, QWidget, QVBoxLayout


from ...core.cache import Cache
from .. import base, Constants
from ..properties import Paths

# Logging
log = logging.getLogger(f"grc.application.{__name__}")


class WorkerSignals(QObject):
    error = Signal(tuple)
    result = Signal(object)
    progress = Signal(tuple)


class Worker(QRunnable):
    """
    This is the Worker that will gather/parse examples as a background task
    """

    def __init__(self, fn, *args, **kwargs):
        super(Worker, self).__init__()

        self.fn = fn
        self.args = args
        self.kwargs = kwargs
        self.signals = WorkerSignals()

        self.kwargs['progress_callback'] = self.signals.progress

    @Slot()
    def run(self):
        try:
            result = self.fn(*self.args, **self.kwargs)
        except:
            print("Error in background task:")
            traceback.print_exc()
        else:
            self.signals.result.emit(result)


class ExampleBrowserDialog(QDialog):
    def __init__(self, browser):
        super(ExampleBrowserDialog, self).__init__()

        self.setMinimumSize(600, 400)
        self.setModal(True)

        self.setWindowTitle("GRC Examples")
        self.browser = browser
        self.layout = QVBoxLayout()
        self.setLayout(self.layout)
        self.layout.addWidget(browser)
        self.browser.connect_dialog(self)


class ExampleBrowser(QWidget, base.Component):
    file_to_open = Signal(str)
    data_role = Qt.UserRole

    lang_dict = {
        'python': 'Python',
        'cpp': 'C++',
    }

    gen_opts_dict = {
        'no_gui': 'No GUI',
        'qt_gui': 'Qt GUI',
        'bokeh_gui': 'Bokeh GUI',
        'hb': 'Hier block ',
        'hb_qt_gui': 'Hier block (Qt GUI)'
    }

    def __init__(self):
        super(ExampleBrowser, self).__init__()
        uic.loadUi(Paths.RESOURCES + "/example_browser_widget.ui", self)
        self.library = None
        self.dialog = None

        self.tree_widget.setHeaderHidden(True)
        self.tree_widget.clicked.connect(self.handle_clicked)

        self.cpp_qt_fg = QPixmap(Paths.RESOURCES + "/cpp_qt_fg.png")
        self.cpp_cmd_fg = QPixmap(Paths.RESOURCES + "/cpp_cmd_fg.png")
        self.py_qt_fg = QPixmap(Paths.RESOURCES + "/py_qt_fg.png")
        self.py_cmd_fg = QPixmap(Paths.RESOURCES + "/py_cmd_fg.png")

        self.examples_dict = self.platform.examples_dict
        self.dir_items = {}

        self.tree_widget.currentItemChanged.connect(self.populate_preview)
        self.tree_widget.itemDoubleClicked.connect(self.open_file)
        self.open_button.clicked.connect(self.open_file)

    def set_library(self, library):
        self.library = library

    def handle_clicked(self):
        if self.tree_widget.isExpanded(self.tree_widget.currentIndex()):
            self.tree_widget.collapse(self.tree_widget.currentIndex())
        else:
            self.tree_widget.expand(self.tree_widget.currentIndex())

    def connect_dialog(self, dialog: QDialog):
        if self.dialog:
            pass  # disconnect?

        self.dialog = dialog
        if isinstance(dialog, ExampleBrowserDialog):
            self.close_button.setHidden(False)
            self.close_button.clicked.connect(dialog.reject)
            self.open_button.clicked.connect(self.done)
            self.tree_widget.itemDoubleClicked.connect(self.done)
        else:
            raise Exception

    def done(self, _=None):
        self.dialog.done(0)

    def populate(self, examples_dict):
        self.examples_dict = examples_dict
        self.tree_widget.clear()

        for path, examples in examples_dict.items():
            for ex in examples:
                rel_path = os.path.relpath(os.path.dirname(ex['path']), path)
                split_rel_path = os.path.normpath(rel_path).split(os.path.sep)
                parent_path = "/".join(split_rel_path[0:-1])
                if rel_path not in self.dir_items:
                    dir_ = None
                    if parent_path:
                        try:
                            dir_ = QTreeWidgetItem(self.dir_items[parent_path])
                        except KeyError:
                            i = 0
                            while i <= len(split_rel_path):
                                partial_path = "/".join(split_rel_path[0:i + 1])
                                split_partial_path = os.path.normpath(partial_path).split(os.path.sep)
                                if not partial_path in self.dir_items:
                                    if i == 0:  # Top level
                                        dir_ = QTreeWidgetItem(self.tree_widget)
                                        dir_.setText(0, partial_path)
                                        self.dir_items[partial_path] = dir_
                                    else:
                                        dir_ = QTreeWidgetItem(self.dir_items["/".join(split_partial_path[:-1])])
                                        dir_.setText(0, split_partial_path[-1])
                                        self.dir_items[partial_path] = dir_
                                i += 1
                    else:
                        dir_ = QTreeWidgetItem(self.tree_widget)
                    dir_.setText(0, split_rel_path[-1])
                    self.dir_items[rel_path] = dir_
                item = QTreeWidgetItem(self.dir_items[rel_path])
                item.setText(0, ex["title"] if ex["title"] else ex["name"])
                item.setData(0, self.data_role, QVariant(ex))

    def reset_preview(self):
        self.title_label.setText(f"<b>Title:</b> ")
        self.author_label.setText(f"<b>Author:</b> ")
        self.language_label.setText(f"<b>Output language:</b> ")
        self.gen_opts_label.setText(f"<b>Type:</b> ")
        self.desc_label.setText('')
        self.image_label.setPixmap(QPixmap())

    def populate_preview(self):
        ex = self.tree_widget.currentItem().data(0, self.data_role)

        self.title_label.setText(f"<b>Title:</b> {ex['title'] if ex else ''}")
        self.author_label.setText(f"<b>Author:</b> {ex['author'] if ex else ''}")
        try:
            self.language_label.setText(
                f"<b>Output language:</b> {self.lang_dict[ex['output_language']] if ex else ''}")
            self.gen_opts_label.setText(f"<b>Type:</b> {self.gen_opts_dict[ex['generate_options']] if ex else ''}")
        except KeyError:
            self.language_label.setText(f"<b>Output language:</b> ")
            self.gen_opts_label.setText(f"<b>Type:</b> ")
        self.desc_label.setText(ex["desc"] if ex else '')

        if ex:
            if ex["output_language"] == "python":
                if ex["generate_options"] == "qt_gui":
                    self.image_label.setPixmap(self.py_qt_fg)
                else:
                    self.image_label.setPixmap(self.py_cmd_fg)
            else:
                if ex["generate_options"] == "qt_gui":
                    self.image_label.setPixmap(self.cpp_qt_fg)
                else:
                    self.image_label.setPixmap(self.cpp_cmd_fg)
        else:
            self.image_label.setPixmap(QPixmap())

    def open_file(self):
        ex = self.tree_widget.currentItem().data(0, self.data_role)
        self.file_to_open.emit(ex["path"])

    def filter_(self, key: str):
        """
        Only display examples that contain a specific block. (Hide the others)

            Parameters:
                key: The key of the block to search for
        """
        found = False
        ex_paths = self.library.get_examples(key)
        for i in range(self.tree_widget.topLevelItemCount()):
            top = self.tree_widget.topLevelItem(i)
            if self.show_selective(top, ex_paths):
                found = True
        return found

    def show_selective(self, item, path):
        item.setHidden(True)
        if item.childCount():  # is a directory
            for i in range(item.childCount()):
                if self.show_selective(item.child(i), path):
                    item.setHidden(False)
            return not item.isHidden()
        else:  # is an example
            ex = item.data(0, self.data_role)
            if ex['path'] in path:
                item.setHidden(False)
                return True
            else:
                return False

    def show_all(self, item):
        item.setHidden(False)
        for i in range(item.childCount()):
            self.show_all(item.child(i))

    def reset(self):
        """Reset the filter, collapse all."""
        self.tree_widget.collapseAll()
        self.reset_preview()

        for i in range(self.tree_widget.topLevelItemCount()):
            top = self.tree_widget.topLevelItem(i)
            self.show_all(top)

    def find_examples(self, progress_callback, ext="grc"):
        """Iterate through the example flowgraph directories and parse them."""
        examples_dict = {}
        try:
            from gnuradio.gr import paths
            cache_file = os.path.join(paths.cache(), Constants.GRC_SUBDIR, Constants.EXAMPLE_CACHE_FILE_NAME)
        except ImportError:
            cache_file = Constants.FALLBACK_EXAMPLE_CACHE_FILE
        with Cache(cache_file, log=False) as cache:
            for entry in self.platform.config.example_paths:
                if entry == '':
                    log.info("Empty example path!")
                    continue
                examples_dict[entry] = []
                if os.path.isdir(entry):
                    subdirs = 0
                    current_subdir = 0
                    for dirpath, dirnames, filenames in os.walk(entry):
                        subdirs += 1  # Loop through once to see how many there are
                    for dirpath, dirnames, filenames in os.walk(entry):
                        dirnames.sort()
                        current_subdir += 1
                        progress_callback.emit((int(100 * current_subdir / subdirs), "Indexing examples"))
                        for filename in sorted(filter(lambda f: f.endswith('.' + ext), filenames)):
                            file_path = os.path.join(dirpath, filename)
                            try:
                                data = cache.get_or_load(file_path)
                                example = {}
                                example["name"] = os.path.basename(file_path)
                                example["generate_options"] = data["options"]["parameters"].get(
                                    "generate_options") or "no_gui"
                                example["output_language"] = data["options"]["parameters"].get(
                                    "output_language") or "python"
                                example["example_filter"] = data["metadata"].get("example_filter") or []
                                example["title"] = data["options"]["parameters"]["title"] or ""
                                example["desc"] = data["options"]["parameters"]["description"] or ""
                                example["author"] = data["options"]["parameters"]["author"] or ""
                                example["path"] = file_path
                                example["module"] = os.path.dirname(file_path).replace(entry, "")
                                if example["module"].startswith("/"):
                                    example["module"] = example["module"][1:]

                                example["blocks"] = set()
                                for block in data["blocks"]:
                                    example["blocks"].add(block["id"])
                                examples_dict[entry].append(example)
                            except Exception:
                                continue

        examples_w_block: dict[str, set[str]] = {}
        designated_examples_w_block: dict[str, set[str]] = {}
        for path, examples in examples_dict.items():
            for example in examples:
                if example["example_filter"]:
                    for block in example["example_filter"]:
                        try:
                            designated_examples_w_block[block].append(example["path"])
                        except KeyError:
                            designated_examples_w_block[block] = [example["path"]]
                    continue
                else:
                    for block in example["blocks"]:
                        try:
                            examples_w_block[block].append(example["path"])
                        except KeyError:
                            examples_w_block[block] = [example["path"]]

        return (examples_dict, examples_w_block, designated_examples_w_block)
