# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import sys

import ida_funcs
import ida_kernwin

from PyQt5.QtCore import (  # noqa: I202
    QAbstractItemModel,
    QModelIndex,
    QObject,
    Qt,
)
from PyQt5.QtGui import QColor
from PyQt5.QtWidgets import QStyledItemDelegate, QWidget
import sip

from .widget import StatusWidget

if sys.version_info > (3,):
    long = int


class Painter(QObject):
    class ProxyItemDelegate(QStyledItemDelegate):
        def __init__(self, delegate, model, parent=None):
            super(Painter.ProxyItemDelegate, self).__init__(parent)
            self._delegate = delegate
            self._model = model

        def paint(self, painter, option, index):
            index = self._model.index(index.row(), index.column())
            self._delegate.paint(painter, option, index)

    class ProxyItemModel(QAbstractItemModel):
        def __init__(self, model, plugin, parent=None):
            super(Painter.ProxyItemModel, self).__init__(parent)
            self._model = model
            self._plugin = plugin

        def index(self, row, column, parent=QModelIndex()):
            return self.createIndex(row, column)

        def parent(self, index):
            index = self._model.index(index.row(), index.column())
            return self._model.parent(index)

        def rowCount(self):  # noqa: N802
            return self._model.rowCount()

        def columnCount(self):  # noqa: N802
            return self._model.columnCount()

        def data(self, index, role=Qt.DisplayRole):
            # Check if disabled by the user
            cursors = self._plugin.config["cursors"]
            if role == Qt.BackgroundRole and cursors["funcs"]:
                func_ea = int(index.sibling(index.row(), 2).data(), 16)
                func = ida_funcs.get_func(func_ea)
                for user in self._plugin.core.get_users().values():
                    if ida_funcs.func_contains(func, user["ea"]):
                        r, g, b = StatusWidget.ida_to_python(user["color"])
                        return QColor(StatusWidget.python_to_qt(r, g, b))
            index = self._model.index(index.row(), index.column())
            return self._model.data(index, role)

    def __init__(self, plugin):
        super(Painter, self).__init__()
        self._plugin = plugin

        self._ida_nav_colorizer = None
        self._nbytes = 0

    def nav_colorizer(self, ea, nbytes):
        """This is the custom nav colorizer used by the painter."""
        self._nbytes = nbytes

        # There is a bug in IDA: with a huge number of segments, all the navbar
        # is colored with the user color. This will be resolved in IDA 7.2.
        cursors = self._plugin.config["cursors"]
        if cursors["navbar"]:
            for user in self._plugin.core.get_users().values():
                # Cursor color
                if ea - nbytes * 2 <= user["ea"] <= ea + nbytes * 2:
                    return long(user["color"])
                # Cursor borders
                if ea - nbytes * 4 <= user["ea"] <= ea + nbytes * 4:
                    return long(0)
        orig = ida_kernwin.call_nav_colorizer(
            self._ida_nav_colorizer, ea, nbytes
        )
        return long(orig)

    def ready_to_run(self):
        # The default nav colorized can only be recovered once!
        ida_nav_colorizer = ida_kernwin.set_nav_colorizer(self.nav_colorizer)
        if ida_nav_colorizer is not None:
            self._ida_nav_colorizer = ida_nav_colorizer
        self.refresh()

    def get_ea_hint(self, ea):
        cursors = self._plugin.config["cursors"]
        if not cursors["navbar"]:
            return None

        for name, user in self._plugin.core.get_users().items():
            start_ea = user["ea"] - self._nbytes * 4
            end_ea = user["ea"] + self._nbytes * 4
            # Check if the navbar range contains the user's address
            if start_ea <= ea <= end_ea:
                return str(name)

    def get_bg_color(self, ea):
        # Check if disabled by the user
        cursors = self._plugin.config["cursors"]
        if not cursors["disasm"]:
            return None

        for user in self._plugin.core.get_users().values():
            if ea == user["ea"]:
                return user["color"]
        return None

    def widget_visible(self, twidget):
        widget = sip.wrapinstance(long(twidget), QWidget)
        if widget.windowTitle() != "Functions window":
            return
        table = widget.layout().itemAt(0).widget()

        # Replace the table's item delegate
        model = Painter.ProxyItemModel(table.model(), self._plugin, self)
        old_deleg = table.itemDelegate()
        new_deleg = Painter.ProxyItemDelegate(old_deleg, model, self)
        table.setItemDelegate(new_deleg)

    def refresh(self):
        ida_kernwin.refresh_navband(True)
        ida_kernwin.request_refresh(ida_kernwin.IWID_DISASMS)
        ida_kernwin.request_refresh(ida_kernwin.IWID_FUNCS)