#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import hal
import gi
import os
import xml.etree.ElementTree as ET
gi.require_version('Gtk', '3.0')
from gi.repository import GLib, Gtk

# ─────────────────────────────────────────────────────────────────────────────
# Farbdefinitionen (CSS)
# ─────────────────────────────────────────────────────────────────────────────
CSS_BIT_ON      = "background-color: #00CC44; color: #000000; font-weight: bold;"
CSS_BIT_OFF     = "background-color: #444444; color: #AAAAAA;"
CSS_BIT_FAULT   = "background-color: #FF3333; color: #FFFFFF; font-weight: bold;"
CSS_BIT_WARN    = "background-color: #FFAA00; color: #000000; font-weight: bold;"
CSS_STATE_OK    = "color: #00CC44; font-weight: bold;"
CSS_STATE_FAULT = "color: #FF3333; font-weight: bold;"
CSS_STATE_IDLE  = "color: #AAAAAA;"
CSS_STATE_WARN  = "color: #FFAA00; font-weight: bold;"

MODE_NAMES = {
    0: "No mode", 1: "Profile Position", 2: "Velocity (VL)",
    3: "Profile Velocity", 4: "Torque Profile", 6: "Homing",
    8: "Cyclic Sync Pos", 9: "Cyclic Sync Vel", 10: "Cyclic Sync Trq"
}

def decode_state(sw):
    fault = bool(sw & (1 << 3)); sod = bool(sw & (1 << 6)); rtso = bool(sw & (1 << 0))
    so = bool(sw & (1 << 1)); oe = bool(sw & (1 << 2)); qs = bool(sw & (1 << 5))
    if fault: return "FAULT", CSS_STATE_FAULT
    if sod: return "Switch On Disabled", CSS_STATE_IDLE
    if oe and so and rtso and qs: return "Operation Enabled ✓", CSS_STATE_OK
    if so and rtso: return "Switched On", CSS_STATE_WARN
    if rtso: return "Ready To Switch On", CSS_STATE_WARN
    if not qs: return "Quick Stop Active", CSS_STATE_FAULT
    return "Not Ready To Switch On", CSS_STATE_IDLE

# ─────────────────────────────────────────────────────────────────────────────
# GladeVCP Handler
# ─────────────────────────────────────────────────────────────────────────────

class HandlerClass:
    UPDATE_INTERVAL_MS = 100

    def __init__(self, halcomp, builder, useropts):
        self.halcomp = halcomp
        self.builder = builder

        # 1. Standard Pins für State Machine
        halcomp.newpin("statusword", hal.HAL_U32, hal.HAL_IN)
        halcomp.newpin("controlword", hal.HAL_U32, hal.HAL_IN)
        halcomp.newpin("modes-op", hal.HAL_U32, hal.HAL_IN)
        halcomp.newpin("slave-online", hal.HAL_BIT, hal.HAL_IN)
        halcomp.newpin("slave-oper", hal.HAL_BIT, hal.HAL_IN)

        # 2. Statische zyklische PDO Pins anlegen (Einheit kW bei Brake Energy hinzugefügt)
        self.pdo_pins = {
            "srv-target-vl": {"type": hal.HAL_S32, "lbl": "lbl_pdo_target_vl", "unit": ""},
            "srv-actual-vl": {"type": hal.HAL_S32, "lbl": "lbl_pdo_actual_vl", "unit": ""},
            "Infos-RO-PDO.speed-rpm": {"type": hal.HAL_S32, "lbl": "lbl_pdo_speed", "unit": "RPM"},
            "Infos-RO-PDO.feedback-rpm": {"type": hal.HAL_S32, "lbl": "lbl_pdo_feedback", "unit": "RPM"},
            "Infos-RO-PDO.power-kw": {"type": hal.HAL_S32, "lbl": "lbl_pdo_power", "unit": "kW", "div": 100.0},
            "Infos-RO-PDO.torque-nm": {"type": hal.HAL_S32, "lbl": "lbl_pdo_torque", "unit": "Nm"},
            "Infos-RO-PDO.torque-pct-highres": {"type": hal.HAL_S32, "lbl": "lbl_pdo_torque_pct", "unit": "%", "div": 10.0},
            "Infos-RO-PDO.brake-energy-avg": {"type": hal.HAL_S32, "lbl": "lbl_pdo_brake", "unit": "kW"},
            "Infos-RO-PDO.dc-link-voltage": {"type": hal.HAL_U32, "lbl": "lbl_pdo_dc_link", "unit": "V"},
        }
        for name, info in self.pdo_pins.items():
            halcomp.newpin(name, info["type"], hal.HAL_IN)
            info["widget"] = builder.get_object(info["lbl"])
            info["last_val"] = None

        # 3. XML modParams & Defaults laden
        self.modparams = {
            "accelDeltaSpeed": "0", "accelDeltaTime": "3",
            "decelDeltaSpeed": "0", "decelDeltaTime": "3",
            "vlDimNumerator": "1", "vlDimDenominator": "1",
            "ramp1Up": "0", "ramp1Down": "0", "ramp2Up": "0", "ramp2Down": "0",
            "jogRampTime": "0", "qstopRampTime": "0",
            "busJog1Speed": "0", "busJog2Speed": "0",
            "digitalRelayCtrl": "0", "tempSlotSource": "1639", "sdoReadConfig": "0"
        }

        xml_path = "/home/cnc/linuxcnc/configs/Test/ethercat-conf_3axes.xml"
        if os.path.exists(xml_path):
            try:
                root = ET.parse(xml_path).getroot()
                for param in root.iter('modParam'):
                    name = param.get('name')
                    if name in self.modparams:
                        self.modparams[name] = param.get('value')
            except Exception as e:
                print(f"[fc302_status_handler] XML Error: {e}")

        # modParams in die GUI eintragen
        for name, val in self.modparams.items():
            lbl = builder.get_object(f"lbl_mp_{name}")
            if lbl: lbl.set_text(str(val))

        # 4. Dynamischen Temperatur-Slot (tempSlotSource) für PDO initialisieren
        ts_val_str = self.modparams.get("tempSlotSource", "1639")
        try:
            ts_par = int(ts_val_str)
        except:
            ts_par = 1639

        self.ts_map = {
            1618: {"pin": "Infos-RO-PDO.motor-thermal-pct",    "type": hal.HAL_U32, "unit": "%",   "title": "Motor Thermisch (PDO):"},
            1619: {"pin": "Infos-RO-PDO.kty-temperature",      "type": hal.HAL_S32, "unit": "°C",  "title": "KTY Temperatur (PDO):"},
            1634: {"pin": "Infos-RO-PDO.heatsink-temp",        "type": hal.HAL_S32, "unit": "°C",  "title": "Kühlkörper Temp (PDO):"},
            1635: {"pin": "Infos-RO-PDO.inverter-thermal-pct", "type": hal.HAL_U32, "unit": "%",   "title": "Umrichter Thermisch (PDO):"},
            1639: {"pin": "Infos-RO-PDO.ctrl-card-temp",       "type": hal.HAL_S32, "unit": "°C",  "title": "Steuerkarte Temp (PDO):"},
        }

        # Falls spezifische Widgets im Glade-Layout existieren, Sichtbarkeit steuern (ein-/ausblenden)
        for par, info in self.ts_map.items():
            suffix = info["pin"].split(".")[-1].replace("-", "_")
            val_lbl = builder.get_object(f"lbl_pdo_{suffix}")
            title_lbl = builder.get_object(f"lbl_title_pdo_{suffix}")
            
            if par == ts_par:
                if val_lbl: val_lbl.set_visible(True)
                if title_lbl: title_lbl.set_visible(True)
            else:
                if val_lbl: val_lbl.set_visible(False)
                if title_lbl: title_lbl.set_visible(False)

        # Aktiven dynamischen PDO Pin registrieren
        if ts_par in self.ts_map:
            ts_info = self.ts_map[ts_par]
            halcomp.newpin(ts_info["pin"], ts_info["type"], hal.HAL_IN)
            
            # Generische Ausweich-Labels (falls genutzt)
            self.lbl_generic_ts_val = builder.get_object("lbl_pdo_temp_slot_val")
            self.lbl_generic_ts_title = builder.get_object("lbl_pdo_temp_slot_title")
            
            if self.lbl_generic_ts_title:
                self.lbl_generic_ts_title.set_text(ts_info["title"])
                self.lbl_generic_ts_title.set_visible(True)
            if self.lbl_generic_ts_val:
                self.lbl_generic_ts_val.set_visible(True)
                
            self.active_ts_pin = ts_info["pin"]
            self.active_ts_unit = ts_info["unit"]
            self.active_ts_suffix = ts_info["pin"].split(".")[-1].replace("-", "_")
            self._last_ts_val = None
        else:
            self.active_ts_pin = None

        # 5. Azyklische SDOs (Infos-RO) basierend auf korrigierter Bitmaske initialisieren
        sdo_val_str = self.modparams.get("sdoReadConfig", "0")
        sdo_config_mask = int(sdo_val_str, 16) if sdo_val_str.lower().startswith('0x') else int(sdo_val_str)

        self.SDO_MAP = {
            0: {"pin": "Infos-RO.motor-thermal-pct",    "lbl": "lbl_sdo_motor_thermal",    "unit": "%",   "type": hal.HAL_U32},
            1: {"pin": "Infos-RO.kty-temperature",      "lbl": "lbl_sdo_kty_temp",         "unit": "°C",  "type": hal.HAL_S32},
            2: {"pin": "Infos-RO.heatsink-temp",        "lbl": "lbl_sdo_heatsink_temp",    "unit": "°C",  "type": hal.HAL_S32},
            3: {"pin": "Infos-RO.inverter-thermal-pct", "lbl": "lbl_sdo_inverter_thermal", "unit": "%",   "type": hal.HAL_U32},
            4: {"pin": "Infos-RO.ctrl-card-temp",       "lbl": "lbl_sdo_ctrl_card_temp",   "unit": "°C",  "type": hal.HAL_S32},
            5: {"pin": "Infos-RO.operating-hours",      "lbl": "lbl_sdo_operating_hours",  "unit": "h",   "type": hal.HAL_U32},
            6: {"pin": "Infos-RO.running-hours",        "lbl": "lbl_sdo_running_hours",    "unit": "h",   "type": hal.HAL_U32},
            7: {"pin": "Infos-RO.kwh-counter",          "lbl": "lbl_sdo_kwh_counter",      "unit": "kWh", "type": hal.HAL_U32},
        }

        self.active_sdos = {}
        for bit, info in self.SDO_MAP.items():
            lbl_widget = builder.get_object(info["lbl"])
            if sdo_config_mask & (1 << bit):
                halcomp.newpin(info["pin"], info["type"], hal.HAL_IN)
                self.active_sdos[bit] = {
                    "pin": info["pin"],
                    "lbl": lbl_widget,
                    "unit": info["unit"],
                    "last_val": None
                }
            else:
                if lbl_widget:
                    lbl_widget.set_text("Deaktiviert")
                    self._set_label_style(lbl_widget, "color: #777777; font-style: italic; font-weight: normal;")

        # Optional: SDO-Busy Pin registrieren, falls SDOs aktiv sind
        if self.active_sdos:
            halcomp.newpin("Infos-RO.sdo-busy", hal.HAL_BIT, hal.HAL_IN)
            self.lbl_sdo_busy = builder.get_object("lbl_sdo_busy")
            self._last_sdo_busy = None

        # 6. Widget Cache für State Machine und Bits aufbauen
        self.w = {
            "slave_status": builder.get_object("lbl_slave_status"),
            "slave_state":  builder.get_object("lbl_slave_state"),
            "mode_num":     builder.get_object("lbl_mode_num"),
            "mode_name":    builder.get_object("lbl_mode_name"),
            "state":        builder.get_object("lbl_state"),
            "sw_hex":       builder.get_object("lbl_sw_hex"),
        }
        self.sw_bits = [builder.get_object(f"sw_bit_{i}") for i in range(16)]
        self.cw_bits = [builder.get_object(f"cw_bit_{i}") for i in range(16)]

        self._last_sw = -1; self._last_cw = -1; self._last_mo = -1
        self._last_onl = None; self._last_op = None

        GLib.timeout_add(self.UPDATE_INTERVAL_MS, self._update)

    @staticmethod
    def _set_label_style(label, css):
        if not label: return
        sc = label.get_style_context()
        provider = Gtk.CssProvider()
        provider.load_from_data(f"label {{ {css} }}".encode())
        sc.add_provider(provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)

    def _update_bit_label(self, label, value, is_f, is_w):
        if not label: return
        label.set_text("1" if value else "0")
        css = CSS_BIT_FAULT if (value and is_f) else (CSS_BIT_WARN if (value and is_w) else (CSS_BIT_ON if value else CSS_BIT_OFF))
        self._set_label_style(label, css)

    def _update(self):
        if self.w["state"].get_allocated_width() <= 1:
            return True

        try:
            # --- 1. State Machine aktualisieren ---
            sw = self.halcomp["statusword"]
            cw = self.halcomp["controlword"]
            mo = self.halcomp["modes-op"]
            online = self.halcomp["slave-online"]
            oper = self.halcomp["slave-oper"]

            if online != self._last_onl or oper != self._last_op:
                self._last_onl, self._last_op = online, oper
                self.w["slave_status"].set_text("ONLINE" if online else "OFFLINE")
                self.w["slave_state"].set_text("OP ✓" if oper else "nicht OP")
                self._set_label_style(self.w["slave_status"], CSS_BIT_ON if oper else CSS_BIT_FAULT)

            if mo != self._last_mo:
                self._last_mo = mo
                self.w["mode_num"].set_text(f"Code: {mo}")
                self.w["mode_name"].set_text(MODE_NAMES.get(mo, "Unknown"))
                self._set_label_style(self.w["mode_name"], CSS_STATE_OK if mo == 2 else CSS_STATE_WARN)

            if sw != self._last_sw:
                self._last_sw = sw
                st_text, st_css = decode_state(sw)
                self.w["sw_hex"].set_text(f"SW: 0x{sw:04X}  ({sw})")
                self.w["state"].set_text(st_text)
                self._set_label_style(self.w["state"], st_css)
                for i in range(16):
                    self._update_bit_label(self.sw_bits[i], bool(sw & (1 << i)), i==3, i==7)

            if cw != self._last_cw:
                self._last_cw = cw
                for i in range(16):
                    self._update_bit_label(self.cw_bits[i], bool(cw & (1 << i)), i==7, False)

            # --- 2. Statische zyklische PDOs aktualisieren ---
            for pin_name, info in self.pdo_pins.items():
                val = self.halcomp[pin_name]
                if val != info["last_val"]:
                    info["last_val"] = val
                    if info["widget"]:
                        display_val = val / info["div"] if "div" in info else val
                        fmt = f"{display_val:.2f}" if "div" in info else f"{display_val}"
                        info["widget"].set_text(f"{fmt} {info['unit']}")

            # --- 3. Dynamischen Temperatur-Slot (tempSlotSource PDO) aktualisieren ---
            if self.active_ts_pin:
                ts_val = self.halcomp[self.active_ts_pin]
                if ts_val != self._last_ts_val:
                    self._last_ts_val = ts_val
                    if self.lbl_generic_ts_val:
                        self.lbl_generic_ts_val.set_text(f"{ts_val} {self.active_ts_unit}")
                    spec_lbl = self.builder.get_object(f"lbl_pdo_{self.active_ts_suffix}")
                    if spec_lbl:
                        spec_lbl.set_text(f"{ts_val} {self.active_ts_unit}")

            # --- 4. Dynamisch erzeugte SDO Pins (Infos-RO) aktualisieren ---
            for bit, sdo in self.active_sdos.items():
                val = self.halcomp[sdo["pin"]]
                if sdo["last_val"] != val:
                    sdo["lbl"].set_text(f"{val} {sdo['unit']}")
                    sdo["last_val"] = val

            # --- 5. SDO-Busy Status anzeigen ---
            if hasattr(self, 'lbl_sdo_busy') and self.lbl_sdo_busy:
                sdo_busy = self.halcomp["Infos-RO.sdo-busy"]
                if sdo_busy != self._last_sdo_busy:
                    self._last_sdo_busy = sdo_busy
                    self.lbl_sdo_busy.set_text("SDO BUSY" if sdo_busy else "SDO IDLE")
                    self._set_label_style(self.lbl_sdo_busy, CSS_BIT_WARN if sdo_busy else CSS_BIT_OFF)

        except Exception as e:
            pass

        return True

def get_handlers(halcomp, builder, useropts):
    return [HandlerClass(halcomp, builder, useropts)]