#!/usr/bin/env python

import os
import sys
import time
from threading import Thread
from functools import partial

# Change to the current directory
os.chdir(os.path.join(os.path.dirname(os.path.realpath(__file__))))

# Append the current directory to the path
sys.path.insert(0, os.getcwd())

# Add the render pipeline to the path
sys.path.insert(0, "../../")

from rplibs.six import iteritems  # noqa
from rplibs.pyqt_imports import * #noqa

# Load the generated UI Layout
from ui.main_window_generated import Ui_MainWindow  # noqa

# 创建一个简单的UniversalMessageDialog类，避免复杂的导入问题
class UniversalMessageDialog(QDialog):
    @staticmethod
    def show_success(parent, title, message, show_cancel=True, confirm_text="确认"):
        msg_box = QMessageBox(QMessageBox.Information, title, message, QMessageBox.Ok, parent)
        if show_cancel:
            msg_box.addButton(QMessageBox.Cancel)
        msg_box.button(QMessageBox.Ok).setText(confirm_text)
        return msg_box.exec_()

    @staticmethod
    def show_warning(parent, title, message, show_cancel=True, confirm_text="确认"):
        msg_box = QMessageBox(QMessageBox.Warning, title, message, QMessageBox.Ok, parent)
        if show_cancel:
            msg_box.addButton(QMessageBox.Cancel)
        msg_box.button(QMessageBox.Ok).setText(confirm_text)
        return msg_box.exec_()

    @staticmethod
    def show_error(parent, title, message, show_cancel=True, confirm_text="确认"):
        msg_box = QMessageBox(QMessageBox.Critical, title, message, QMessageBox.Ok, parent)
        if show_cancel:
            msg_box.addButton(QMessageBox.Cancel)
        msg_box.button(QMessageBox.Ok).setText(confirm_text)
        return msg_box.exec_()

    @staticmethod
    def show_info(parent, title, message, show_cancel=True, confirm_text="确认"):
        msg_box = QMessageBox(QMessageBox.Information, title, message, QMessageBox.Ok, parent)
        if show_cancel:
            msg_box.addButton(QMessageBox.Cancel)
        msg_box.button(QMessageBox.Ok).setText(confirm_text)
        return msg_box.exec_()

from rpcore.pluginbase.manager import PluginManager  # noqa
from rpcore.util.network_communication import NetworkCommunication  # noqa
from rpcore.mount_manager import MountManager  # noqa


class PluginConfigurator(QMainWindow, Ui_MainWindow):

    """ Interface to change the plugin settings """

    def __init__(self):
        

        # Init mounts
        self._mount_mgr = MountManager(None)
        self._mount_mgr.mount()

        self._plugin_mgr = PluginManager(None)
        self._plugin_mgr.requires_daytime_settings = False

        QMainWindow.__init__(self)
        Ui_MainWindow.__init__(self)
        self.setupUi(self)



        self._current_plugin = None
        self._current_plugin_instance = None
        self.lbl_restart_pipeline.hide()
        self._set_settings_visible(False)
        self._update_queue = list()

        qt_connect(self.lst_plugins, "itemSelectionChanged()",
                self.on_plugin_selected)
        qt_connect(self.lst_plugins, "itemChanged(QListWidgetItem*)",
                self.on_plugin_state_changed)
        qt_connect(self.btn_reset_plugin_settings, "clicked()",
                self.on_reset_plugin_settings)

        self._load_plugin_list()

        # Adjust column widths
        self.table_plugin_settings.setColumnWidth(0, 140)
        self.table_plugin_settings.setColumnWidth(1, 105)
        self.table_plugin_settings.setColumnWidth(2, 160)

        update_thread = Thread(target=self.update_thread, args=())
        update_thread.start()

        # 启动通信文件监听线程
        comm_thread = Thread(target=self.communication_thread, args=())
        comm_thread.daemon = True  # 设置为守护线 程，主程序退出时自动结束
        comm_thread.start()

    def closeEvent(self, event):  # noqa
        event.accept()
        # 通知主程序配置可能已更改
        try:
            # 发送一个信号或者写入一个标记文件来通知主程序
            marker_file = "/tmp/rp_plugin_config_changed.flag"
            with open(marker_file, "w") as f:
                f.write("Plugin configuration may have changed")
        except Exception as e:
            print(f"无法创建配置更改标记文件: {e}")
        
        import os
        os._exit(1)

    def on_reset_plugin_settings(self):
        """ Gets called when the user wants to reset settings of a plugin """

        # Ask the user if he's really sure about it
        msg = "Are you sure you want to reset the settings of '"
        msg += self._current_plugin_instance.name + "'?\n"
        msg += "This does not reset the Time of Day settings of this plugin.\n\n"
        msg += "!! This cannot be undone !! They will be lost forever (a long time!)."
        reply = QMessageBox.question(
            self, "Warning", msg, QMessageBox.Yes, QMessageBox.No)
        if reply == QMessageBox.Yes:

            UniversalMessageDialog.show_success(
                self,
                "Success",
                "Settings have been reset! You may have to restart the pipeline.",
                show_cancel=False,
                confirm_text="OK"
            )
            self._plugin_mgr.reset_plugin_settings(self._current_plugin)

            # Save config
            self._plugin_mgr.save_overrides("/$$rpconfig/plugins.yaml")

            # Always show the restart hint, even if its not always required
            self._show_restart_hint()

            # Re-render everything
            self._load_plugin_list()

    def on_plugin_state_changed(self, item):
        """ Handler when a plugin got activated/deactivated """
        plugin_id = item._plugin_id
        state = item.checkState() == Qt.Checked
        self._plugin_mgr.set_plugin_enabled(plugin_id, state)
        self._rewrite_plugin_config()
        self._show_restart_hint()

    def on_plugin_selected(self):
        """ Gets called when a plugin got selected in the plugin list """
        selected_item = self.lst_plugins.selectedItems()
        if not selected_item:
            self._current_plugin = None
            self._current_plugin_instance = None
            self._set_settings_visible(False)
            return
        assert len(selected_item) == 1
        selected_item = selected_item[0]
        self._current_plugin = selected_item._plugin_id
        self._current_plugin_instance = self._plugin_mgr.instances[self._current_plugin]
        assert(self._current_plugin_instance is not None)
        self._render_current_plugin()
        self._set_settings_visible(True)

    def update_thread(self):
        """ Internal update thread """
        while True:
            if len(self._update_queue) > 0:
                item = self._update_queue.pop(-1)
                NetworkCommunication.send_async(NetworkCommunication.CONFIG_PORT, item)

                if item.startswith("setval "):
                    setting_id = item.split()[1]
                    for entry in list(self._update_queue):
                        if entry.split()[1] == setting_id:
                            self._update_queue.remove(entry)
            time.sleep(0.2)

    def _rewrite_plugin_config(self):
        """ Rewrites the plugin configuration """
        self._plugin_mgr.save_overrides("/$$rpconfig/plugins.yaml")

    def _render_current_plugin(self):
        """ Displays the currently selected plugin """
        self.lbl_plugin_name.setText(self._current_plugin_instance.name.upper() + " <span style='color: #999; margin-left: 5px;'>[" + self._current_plugin_instance.plugin_id + "]</span>")

        version_str = "Version " + self._current_plugin_instance.version
        version_str += " by " + self._current_plugin_instance.author

        self.lbl_plugin_version.setText(version_str)
        self.lbl_plugin_desc.setText(self._current_plugin_instance.description)

        self._render_current_settings()

    def _show_restart_hint(self):
        """ Shows a hint to restart the pipeline """
        self.lbl_restart_pipeline.show()

    def _render_current_settings(self):
        """ Renders the current plugin settings """
        settings = self._plugin_mgr.settings[self._current_plugin]

        # remove all rows
        while self.table_plugin_settings.rowCount() > 0:
            self.table_plugin_settings.removeRow(0)

        label_font = QFont()
        label_font.setPointSize(10)
        label_font.setFamily("Roboto")

        desc_font = QFont()
        desc_font.setPointSize(8)
        desc_font.setFamily("Roboto")

        for index, (name, handle) in enumerate(iteritems(settings)):
            if not handle.should_be_visible(settings):
                continue

            row_index = self.table_plugin_settings.rowCount()

            # Increase row count
            self.table_plugin_settings.insertRow(self.table_plugin_settings.rowCount())

            label = QLabel()
            label.setText(handle.label)
            label.setWordWrap(True)
            label.setFont(label_font)

            if not (handle.shader_runtime or handle.runtime ):
                label.setStyleSheet("color: #999;")

            if handle.display_conditions:
                label.setStyleSheet(label.styleSheet() + "padding-left: 10px;")

            label.setMargin(10)

            self.table_plugin_settings.setCellWidget(row_index, 0, label)

            item_default = QTableWidgetItem()
            item_default.setText(str(handle.default))
            item_default.setTextAlignment(Qt.AlignCenter)
            self.table_plugin_settings.setItem(row_index, 1, item_default)

            setting_widget = self._get_widget_for_setting(name, handle)
            self.table_plugin_settings.setCellWidget(row_index, 2, setting_widget)

            label_desc = QLabel()
            label_desc.setText(handle.description)
            label_desc.setWordWrap(True)
            label_desc.setFont(desc_font)
            label_desc.setStyleSheet("color: #555;padding: 5px;")

            self.table_plugin_settings.setCellWidget(row_index, 3, label_desc)

    def _do_update_setting(self, setting_id, value):
        """ Updates a setting of the current plugin """

        # Check whether the setting is a runtime setting
        setting_handle = self._plugin_mgr.get_setting_handle(
            self._current_plugin, setting_id)

        # Skip the setting in case the value is equal
        if setting_handle.value == value:
            return

        # Otherwise set the new value
        setting_handle.set_value(value)
        self._rewrite_plugin_config()

        if not setting_handle.runtime and not setting_handle.shader_runtime:
            self._show_restart_hint()
        else:
            # In case the setting is dynamic, notice the pipeline about it:
            # print("Sending reload packet ...")
            self._update_queue.append("setval {}.{} {}".format(
                self._current_plugin, setting_id, value))

        # Update GUI, but only in case of enum and bool values, since they can trigger
        # display conditions:
        if setting_handle.type == "enum" or setting_handle.type == "bool":
            self._render_current_settings()

    def _on_setting_bool_changed(self, setting_id, value):
        self._do_update_setting(setting_id, value == Qt.Checked)

    def _on_setting_scalar_changed(self, setting_id, value):
        self._do_update_setting(setting_id, value)

    def _on_setting_enum_changed(self, setting_id, value):
        self._do_update_setting(setting_id, value)

    def _on_setting_power_of_two_changed(self, setting_id, value):
        self._do_update_setting(setting_id, value)

    def _on_setting_slider_changed(self, setting_id, setting_type, bound_objs, value):
        if setting_type == "float":
            value /= 100000.0
        self._do_update_setting(setting_id, value)

        for obj in bound_objs:
            obj.setValue(value)

    def _on_setting_spinbox_changed(self, setting_id, setting_type, bound_objs, value):
        self._do_update_setting(setting_id, value)
        # Assume objects are sliders, so we need to rescale the value
        for obj in bound_objs:
            obj.setValue(value * 100000.0 if setting_type == "float" else value)

    def _choose_path(self, setting_id, setting_handle, bound_objs):
        """ Shows a file chooser to show an path from """

        this_dir = os.path.dirname(os.path.realpath(__file__))
        plugin_dir = os.path.join(this_dir, "../../rpplugins/" + self._current_plugin, "resources")
        plugin_dir = os.path.abspath(plugin_dir)
        search_dir = os.path.join(plugin_dir, setting_handle.base_path)

        print("Plugin dir =", plugin_dir)
        print("Search dir =", search_dir)

        current_file = setting_handle.value.replace("\\", "/").split("/")[-1]
        print("Current file =", current_file)
        file_dlg = QFileDialog(self, "Choose File ..", search_dir, setting_handle.file_type)
        file_dlg.selectFile(current_file)
        # file_dlg.setViewMode(QFileDialog.Detail)

        if file_dlg.exec_():
            filename = file_dlg.selectedFiles()
            filename = filename[-1]
            print("QT selected files returned:", filename)

            filename = os.path.relpath(str(filename), plugin_dir)
            filename = filename.replace("\\", "/")
            print("Relative path is", filename)
            self._do_update_setting(setting_id, filename)

            display_file = filename.split("/")[-1]
            for obj in bound_objs:
                obj.setText(display_file)

    def _get_widget_for_setting(self, setting_id, setting):
        """ Returns an appropriate widget to control the given setting """

        widget = QWidget()
        layout = QHBoxLayout()
        layout.setAlignment(Qt.AlignCenter)
        widget.setLayout(layout)


        if setting.type == "bool":
            box = QCheckBox()
            box.setChecked(Qt.Checked if setting.value else Qt.Unchecked)
            qt_connect(box, "stateChanged(int)",
                    partial(self._on_setting_bool_changed, setting_id))
            layout.addWidget(box)

        elif setting.type == "float" or setting.type == "int":

            if setting.type == "float":
                box = QDoubleSpinBox()

                if setting.maxval - setting.minval <= 2.0:
                    box.setDecimals(4)
            else:
                box = QSpinBox()
            box.setMinimum(setting.minval)
            box.setMaximum(setting.maxval)
            box.setValue(setting.value)

            box.setAlignment(Qt.AlignCenter)

            slider = QSlider()
            slider.setOrientation(Qt.Horizontal)

            if setting.type == "float":
                box.setSingleStep(abs(setting.maxval - setting.minval) / 100.0)
                slider.setMinimum(int(setting.minval * 100000.0))
                slider.setMaximum(int(setting.maxval * 100000.0))
                slider.setValue(int(setting.value * 100000.0))
            elif setting.type == "int":
                box.setSingleStep(int(max(1, (setting.maxval - setting.minval) / 32)))
                slider.setMinimum(int(setting.minval))
                slider.setMaximum(int(setting.maxval))
                slider.setValue(int(setting.value))

            layout.addWidget(box)
            layout.addWidget(slider)

            qt_connect(slider, "valueChanged(int)",
                    partial(self._on_setting_slider_changed, setting_id, setting.type, [box]))

            value_type = "int" if setting.type == "int" else "double"

            qt_connect(box, "valueChanged(" + value_type + ")",
                    partial(self._on_setting_spinbox_changed, setting_id, setting.type, [slider]))

        elif setting.type == "enum":
            box = QComboBox()
            for value in setting.values:
                box.addItem(value)
            qt_connect(box, "currentIndexChanged(QString)",
                    partial(self._on_setting_enum_changed, setting_id))
            box.setCurrentIndex(setting.values.index(setting.value))
            box.setMinimumWidth(145)

            layout.addWidget(box)

        elif setting.type == "power_of_two":

            box = QComboBox()
            resolutions = [str(2**i) for i in range(1, 32) if 2**i >= setting.minval and 2**i <= setting.maxval]
            for value in resolutions:
                box.addItem(value)
            qt_connect(box, "currentIndexChanged(QString)",
                    partial(self._on_setting_power_of_two_changed, setting_id))
            box.setCurrentIndex(resolutions.index(str(setting.value)))
            box.setMinimumWidth(145)
            layout.addWidget(box)

        elif setting.type == "sample_sequence":

            box = QComboBox()
            for value in setting.sequences:
                box.addItem(value)
            qt_connect(box, "currentIndexChanged(QString)",
                    partial(self._on_setting_enum_changed, setting_id))
            box.setCurrentIndex(setting.sequences.index(str(setting.value)))
            box.setMinimumWidth(145)
            layout.addWidget(box)

        elif setting.type == "path":

            label = QLabel()
            display_file = setting.value.replace("\\", "/").split("/")[-1]

            desc_font = QFont()
            desc_font.setPointSize(7)
            desc_font.setFamily("Roboto")

            label.setText(display_file)
            label.setFont(desc_font)

            button = QPushButton()
            button.setText("Choose File ...")
            qt_connect(button, "clicked()", partial(
                self._choose_path, setting_id, setting, (label,)))

            layout.addWidget(label)
            layout.addWidget(button)

        else:
            print("ERROR: Unkown setting type:", setting.type)

        return widget

    def _set_settings_visible(self, flag):
        """ Sets whether the settings panel is visible or not """
        if flag:
            self.frame_details.show()
        else:
            self.frame_details.hide()

    def _load_plugin_list(self):
        """ Reloads the whole plugin list """
        print("Loading plugin list")

        # Reset selection
        self._current_plugin = None
        self._current_plugin_instance = None
        self._set_settings_visible(False)

        # Plugins are all plugins in the plugins directory
        self._plugin_mgr.unload()
        self._plugin_mgr.load()

        self.lst_plugins.clear()
        plugins = sorted(iteritems(self._plugin_mgr.instances), key=lambda plg: plg[1].name)


        item_font = QFont()
        item_font.setBold(False)
        item_font.setPointSize(10)

        for plugin_id, instance in plugins:

            item = QListWidgetItem()
            item.setText(" " + instance.name)
            item.setFont(item_font)

            if self._plugin_mgr.is_plugin_enabled(plugin_id):
                item.setCheckState(Qt.Checked)
            else:
                item.setCheckState(Qt.Unchecked)

            item._plugin_id = plugin_id
            self.lst_plugins.addItem(item)

        self.lst_plugins.setCurrentRow(0)

    def communication_thread(self):
        """监听来自主程序的通信文件"""
        import json
        import tempfile

        comm_dir = os.path.join(tempfile.gettempdir(), "daytime_editor_comm")
        comm_file = os.path.join(comm_dir, "sun_azimuth_command.json")

        print("🔄 Day Time Editor通信监听已启动")
        print(f"   监听文件: {comm_file}")

        last_timestamp = 0

        while True:
            try:
                if os.path.exists(comm_file):
                    with open(comm_file, 'r') as f:
                        command = json.load(f)

                    # 检查是否是新命令（避免重复执行）
                    if command.get('timestamp', 0) > last_timestamp:
                        last_timestamp = command.get('timestamp', 0)

                        if command.get('command') == 'set_sun_azimuth':
                            azimuth_value = command.get('value', 0)
                            print(f"📨 收到Sun Azimuth命令: {azimuth_value}°")

                            # 在主线程中执行UI更新
                            QTimer.singleShot(0, partial(self.apply_sun_azimuth, azimuth_value))

                time.sleep(0.1)  # 100ms检查一次

            except Exception as e:
                print(f"⚠️ 通信监听错误: {e}")
                time.sleep(1)  # 出错时等待1秒再重试

    def apply_sun_azimuth(self, azimuth_degrees):
        """应用Sun Azimuth值到Day Time Editor"""
        try:
            print(f"🌞 正在应用Sun Azimuth: {azimuth_degrees}°")

            # 查找Sun Azimuth相关的控件
            # 这里需要根据实际的UI结构来实现
            # 先尝试找到可能的控件

            # 方法1：查找包含"azimuth"的控件
            for widget_name in dir(self):
                widget = getattr(self, widget_name, None)
                if widget and hasattr(widget, 'setValue'):
                    if 'azimuth' in widget_name.lower() or 'sun' in widget_name.lower():
                        print(f"   尝试设置控件: {widget_name}")
                        try:
                            widget.setValue(azimuth_degrees)
                            print(f"✅ 成功设置 {widget_name} = {azimuth_degrees}°")
                            return
                        except Exception as e:
                            print(f"   设置失败: {e}")

            # 方法2：查找表格中的Sun Azimuth设置
            if hasattr(self, 'table_plugin_settings'):
                table = self.table_plugin_settings
                for row in range(table.rowCount()):
                    item = table.item(row, 0)  # 第一列通常是设置名称
                    if item and 'azimuth' in item.text().lower():
                        # 找到Sun Azimuth行，设置第二列的值
                        value_item = table.item(row, 1)
                        if value_item:
                            value_item.setText(str(azimuth_degrees))
                            print(f"✅ 在表格中设置Sun Azimuth = {azimuth_degrees}°")
                            return

            print("⚠️ 未找到Sun Azimuth控件，请检查UI结构")

        except Exception as e:
            print(f"❌ 应用Sun Azimuth失败: {e}")
            import traceback
            traceback.print_exc()

# Start application
app = QApplication(sys.argv)
qt_register_fonts()
configurator = PluginConfigurator()
configurator.show()
app.exec_()
