from PySide2.QtCore import Qt
from PySide2.QtWidgets import QWidget, QVBoxLayout, QLineEdit, QTreeWidget, QTreeWidgetItem, QPushButton

from angr.analyses.decompiler.decompilation_options import DecompilationOption, options as dec_options
from angr.analyses.decompiler.optimization_passes import get_optimization_passes, get_default_optimization_passes


class OptionType:
    OPTION = 1
    OPTIMIZATION_PASS = 2


class QDecompilationOption(QTreeWidgetItem):
    def __init__(self, parent, option, type_: int, enabled=True):
        super().__init__(parent)
        self.option = option
        self.type = type_

        if self.type == OptionType.OPTIMIZATION_PASS:
            self.setText(0, option.__name__)
        elif self.type == OptionType.OPTION:
            self.setText(0, option.name)
            self.setToolTip(0, option.description)
        else:
            raise NotImplementedError("Unsupported option type %s." % self.type_)

        self.setFlags(self.flags() | Qt.ItemIsUserCheckable)
        if enabled:
            self.setCheckState(0, Qt.Checked)
        else:
            self.setCheckState(0, Qt.Unchecked)


class QDecompilationOptions(QWidget):
    def __init__(self, code_view, instance, options=None, passes=None):
        super().__init__()

        self._code_view = code_view
        self._instance = instance
        self._options = options
        self._opti_passes = passes

        # widgets
        self._search_box = None  # type:QLineEdit
        self._treewidget = None  # type:QTreeWidget
        self._apply_btn = None  # type:QPushButton

        self._qoptions = [ ]
        self._qoptipasses = [ ]

        self._init_widgets()

        self.reload()

    def reload(self, force=False):
        if force or self._options is None:
            self._options = self.get_default_options()

        if force or self._opti_passes is None:
            if self._instance.project is not None:
                self._opti_passes = self.get_all_passes()
            else:
                self._opti_passes = []

        self._reload_options()

    @property
    def selected_passes(self):
        selected = [ ]
        for item in self._qoptipasses:
            if item.checkState(0):
                selected.append(item.option)
        return selected

    @property
    def option_and_values(self):
        ov = [ ]
        for item in self._qoptions:
            if item.checkState(0):
                ov.append((item.option, True))
            else:
                ov.append((item.option, False))
        return ov

    @property
    def options(self):
        return self._options

    @options.setter
    def options(self, v):
        self._options = v
        self._reload_options()

    def get_default_options(self):
        return dec_options

    def get_default_passes(self):
        if self._instance is None or self._instance.project is None:
            return set()
        return get_default_optimization_passes(self._instance.project.arch, self._instance.project.simos.name)

    def get_all_passes(self):
        return get_optimization_passes(self._instance.project.arch, self._instance.project.simos.name)

    def _init_widgets(self):

        # search box
        self._search_box = QLineEdit()

        # tree view
        self._treewidget = QTreeWidget()
        self._treewidget.setHeaderHidden(True)

        # refresh button
        self._apply_btn = QPushButton("Apply")
        self._apply_btn.clicked.connect(self._code_view.decompile)

        layout = QVBoxLayout()
        layout.addWidget(self._search_box)
        layout.addWidget(self._treewidget)
        layout.addWidget(self._apply_btn)

        self.setLayout(layout)

    def _reload_options(self):

        self._treewidget.clear()
        self._qoptions.clear()
        self._qoptipasses.clear()

        categories = { }

        # populate the tree widget with new options
        for option in sorted(self._options, key=lambda x: x.name):
            if option.category in categories:
                category = categories[option.category]
            else:
                category = QTreeWidgetItem(self._treewidget, [option.category])

            w = QDecompilationOption(category, option, OptionType.OPTION, enabled=option.default_value==True)
            self._qoptions.append(w)

        passes_category = QTreeWidgetItem(self._treewidget, ["Optimization Passes"])
        categories['passes'] = passes_category

        default_passes = set(self.get_default_passes())
        for pass_ in self._opti_passes:
            w = QDecompilationOption(passes_category, pass_, OptionType.OPTIMIZATION_PASS,
                                     enabled=pass_ in default_passes)
            self._qoptipasses.append(w)

        # expand all
        self._treewidget.expandAll()