# -*- coding: utf-8 -*-
"""
Simplified 1553 Debug View for embedding in main ARTOS GUI.

Shows only:
- Bus Monitor table (messages with stats)
- Details pane (selected message fields)

Uses existing BusMonitorCore instance from main application.
Logs to main GUI logger (no internal log widget).
"""
import tkinter as tk
from tkinter import ttk
import logging
import time


class DebugView(tk.Frame):
    """
    Lightweight 1553 debug panel showing message table and details.
    Designed to be embedded in a Toplevel, uses external BusMonitorCore.
    """
    
    def __init__(self, parent, bus_monitor):
        """
        Args:
            parent: Tk parent widget
            bus_monitor: BusMonitorCore instance (already initialized)
        """
        super().__init__(parent)
        self.pack(fill=tk.BOTH, expand=True)
        
        self.bus_monitor = bus_monitor
        self.logger = logging.getLogger('ARTOS.1553Debug')
        
        # Cache for tree items and message wrappers
        self._tree_items = {}
        self._current_selected_label = None
        
        self._create_widgets()
        self._start_update_loop()
        
        self.logger.info("1553 Debug View initialized")
    
    def _create_widgets(self):
        """Create Bus Monitor table and Details pane."""
        # Top controls
        controls = tk.Frame(self)
        controls.pack(side=tk.TOP, fill=tk.X, padx=6, pady=6)
        
        tk.Label(controls, text="1553 Bus Monitor - Real-time", font=('Arial', 10, 'bold')).pack(side=tk.LEFT, padx=10)
        
        refresh_btn = tk.Button(controls, text="Refresh", command=self._refresh_table)
        refresh_btn.pack(side=tk.RIGHT, padx=4)
        
        # Main content: table (left) + details (right)
        content = tk.Frame(self)
        content.pack(side=tk.TOP, fill=tk.BOTH, expand=True, padx=6, pady=2)
        
        # Left: Bus Monitor table
        table_frame = tk.LabelFrame(content, text="Bus Monitor")
        table_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=(0, 6))
        
        columns = ("label", "cw", "sw", "num", "errs", "period", "mc")
        self.tree = ttk.Treeview(table_frame, columns=columns, show="headings", height=20)
        self.tree.heading("label", text="Name")
        self.tree.heading("cw", text="RT-SA-WC-T/R")
        self.tree.heading("sw", text="SW")
        self.tree.heading("num", text="Num")
        self.tree.heading("errs", text="Errs")
        self.tree.heading("period", text="period")
        self.tree.heading("mc", text="MC")
        
        self.tree.column("label", width=120)
        self.tree.column("cw", width=120)
        self.tree.column("sw", width=60)
        self.tree.column("num", width=60)
        self.tree.column("errs", width=60)
        self.tree.column("period", width=80)
        self.tree.column("mc", width=60)
        
        scrollbar = ttk.Scrollbar(table_frame, orient=tk.VERTICAL, command=self.tree.yview)
        self.tree.configure(yscrollcommand=scrollbar.set)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        self.tree.pack(fill=tk.BOTH, expand=True, padx=6, pady=6)
        
        self.tree.bind('<<TreeviewSelect>>', self._on_tree_select)
        
        # Right: Details pane
        details_frame = tk.LabelFrame(content, text="Message Details")
        details_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=False, padx=6)
        
        detail_columns = ("param", "value")
        self.detail_tree = ttk.Treeview(details_frame, columns=detail_columns, show="headings", height=20)
        self.detail_tree.heading("param", text="Parameter")
        self.detail_tree.heading("value", text="Value")
        self.detail_tree.column("param", width=200)
        self.detail_tree.column("value", width=150)
        
        detail_scroll = ttk.Scrollbar(details_frame, orient=tk.VERTICAL, command=self.detail_tree.yview)
        self.detail_tree.configure(yscrollcommand=detail_scroll.set)
        detail_scroll.pack(side=tk.RIGHT, fill=tk.Y)
        self.detail_tree.pack(fill=tk.BOTH, expand=True, padx=6, pady=6)
    
    def _get_messagedb(self):
        """Get MessageDB from BusMonitorCore."""
        if self.bus_monitor is None:
            return None
        return getattr(self.bus_monitor, '_messagedb', None)
    
    def _refresh_table(self):
        """Refresh the message table from MessageDB."""
        messagedb = self._get_messagedb()
        if not messagedb:
            self.logger.warning("MessageDB not available")
            return
        
        try:
            all_messages = messagedb.getAllMessages()
            
            for label, wrapper in all_messages.items():
                try:
                    # Use the same attributes as MonitorApp for compatibility
                    # CW from head.cw.raw
                    try:
                        cw_raw = getattr(wrapper.head.cw, 'raw', None)
                    except Exception:
                        cw_raw = None
                    
                    # SW from head.sw
                    try:
                        sw = getattr(wrapper.head, 'sw', 0)
                    except Exception:
                        sw = 0
                    
                    # Num = sent_count (or size as fallback)
                    num = getattr(wrapper, 'sent_count', getattr(wrapper, 'size', 0))
                    
                    # Errs from head.errcode
                    try:
                        errs = getattr(wrapper.head, 'errcode', 0)
                    except Exception:
                        errs = 0
                    
                    # Period from _time_ms or calculated from freq
                    period_ms = getattr(wrapper, '_time_ms', None)
                    if period_ms is None:
                        freq = getattr(wrapper, 'freq', None)
                        if freq and freq > 0:
                            period_ms = 1000.0 / freq
                        else:
                            period_ms = 0
                    
                    # MC = recv_count
                    mc = getattr(wrapper, 'recv_count', 0)
                    
                    # Format CW as RT-SA-WC-T/R
                    try:
                        rt = getattr(wrapper.head.cw.str, 'rt', 0)
                        sa = getattr(wrapper.head.cw.str, 'sa', 0)
                        wc = getattr(wrapper.head.cw.str, 'wc', 0)
                        tr = getattr(wrapper.head.cw.str, 'tr', 0)
                        tr_str = "T" if tr == 0 else "R"
                        cw_str = f"{rt:02d}-{sa:02d}-{wc:02d}-{tr_str}"
                    except Exception:
                        cw_str = "---"
                    
                    values = (
                        label,
                        cw_str,
                        f"{sw}",
                        f"{num}",
                        f"{errs}",
                        f"{period_ms:.1f}ms" if period_ms > 0 else "---",
                        f"{mc}"
                    )
                    
                    # Update or insert
                    if label in self._tree_items:
                        item_id = self._tree_items[label]
                        self.tree.item(item_id, values=values)
                    else:
                        item_id = self.tree.insert('', tk.END, values=values)
                        self._tree_items[label] = item_id
                
                except Exception as e:
                    self.logger.debug(f"Error updating row for {label}: {e}")
        
        except Exception as e:
            self.logger.error(f"Error refreshing table: {e}")
    
    def _on_tree_select(self, event):
        """Handle tree selection - show message details."""
        selection = self.tree.selection()
        if not selection:
            return
        
        item_id = selection[0]
        values = self.tree.item(item_id, 'values')
        if not values:
            return
        
        label = values[0]
        self._current_selected_label = label
        self._update_details(label)
    
    def _update_details(self, label):
        """Show details for selected message - display payload fields with raw and enum values."""
        # Clear existing details
        for item in self.detail_tree.get_children():
            self.detail_tree.delete(item)
        
        messagedb = self._get_messagedb()
        if not messagedb:
            return
        
        try:
            all_messages = messagedb.getAllMessages()
            if label not in all_messages:
                return
            
            wrapper = all_messages[label]
            
            # Show message label header
            self._add_detail_row("=== Message ===", label)
            
            # Show message payload fields with raw and enum values
            try:
                msg = wrapper.message
                if msg:
                    self._extract_message_fields(msg)
                else:
                    self._add_detail_row("No payload data", "---")
            except Exception as e:
                self._add_detail_row("Error reading payload", str(e))
        
        except Exception as e:
            self.logger.error(f"Error updating details for {label}: {e}")
    
    def _get_enum_items(self, class_name):
        """Return list of (name, value) tuples for enum class, or None."""
        try:
            from .monitor_helpers import get_enum_items
            return get_enum_items(class_name)
        except Exception:
            return None
    
    def _get_enum_for_field(self, field_name):
        """Return enum class for field_name from ENUM_MAP, or None."""
        try:
            import Grifo_E_1553lib.data_types.enum_map as em
            return em.ENUM_MAP.get(field_name)
        except Exception:
            try:
                import pybusmonitor1553.Grifo_E_1553lib.data_types.enum_map as em
                return em.ENUM_MAP.get(field_name)
            except Exception:
                return None
    
    def _is_struct_like(self, v):
        """Check if value is a struct-like object - same logic as monitor.py."""
        if v is None:
            return False
        if isinstance(v, (int, float, str, bytes)):
            return False
        if callable(v):
            return False
        # ctypes.Structure expose _fields_
        if hasattr(v, '_fields_'):
            return True
        # objects with many public attributes are likely struct-like
        public = [n for n in dir(v) if not n.startswith('_')]
        if len(public) > 2:
            return True
        return False
    
    def _extract_message_fields(self, obj, prefix="", _depth=0, max_depth=5):
        """Recursively extract fields from ctypes message structure - mimics monitor.py formatting."""
        if _depth >= max_depth:
            return
        
        try:
            # Iterate over public attributes like monitor.py does
            for name in [n for n in dir(obj) if not n.startswith('_')]:
                try:
                    val = getattr(obj, name)
                except Exception:
                    continue
                
                if callable(val):
                    continue
                
                full_name = f"{prefix}.{name}" if prefix else name
                
                # Special-case: ctypes Union with a '.str' structure (bitfields)
                if hasattr(val, 'str') and self._is_struct_like(getattr(val, 'str')):
                    # Show structure header
                    indent = "  " * _depth
                    self._add_detail_row(f"{indent}{name}", "")
                    # Recurse into the .str sub-structure
                    try:
                        self._extract_message_fields(getattr(val, 'str'), prefix=f"{full_name}.str", _depth=_depth+1, max_depth=max_depth)
                    except Exception:
                        pass
                    continue
                
                # Group nested structs (ctypes structs or objects with many public attrs)
                if self._is_struct_like(val) and not (hasattr(val, 'raw') or hasattr(val, 'value')):
                    # Show structure header
                    indent = "  " * _depth
                    self._add_detail_row(f"{indent}{name}", "")
                    # Recurse
                    self._extract_message_fields(val, prefix=full_name, _depth=_depth+1, max_depth=max_depth)
                    continue
                
                # Scalar value or enum - show with proper formatting
                indent = "  " * (_depth + 1)
                
                # Get raw value
                if hasattr(val, 'value'):
                    raw_val = val.value
                elif hasattr(val, 'raw'):
                    raw_val = val.raw
                else:
                    raw_val = val
                
                # Try to get enum representation - use ENUM_MAP first (by field name)
                enum_items = None
                try:
                    # Try ENUM_MAP first using field name
                    enum_cls = self._get_enum_for_field(name)
                    if enum_cls:
                        enum_items = [(m.name, m.value) for m in enum_cls]
                    else:
                        # Fallback to class-based lookup
                        enum_items = self._get_enum_items(val.__class__.__name__)
                except Exception:
                    pass
                
                # Format the value display like monitor.py: "ENUM_NAME (value)" or just value
                if enum_items:
                    try:
                        raw = int(raw_val)
                        # Find matching enum name
                        enum_name = None
                        for (n, v) in enum_items:
                            if int(v) == raw:
                                enum_name = n
                                break
                        if enum_name:
                            val_str = f"{enum_name} ({raw})"
                        else:
                            val_str = str(raw)
                    except Exception:
                        val_str = str(raw_val)
                else:
                    # No enum, just show the value
                    val_str = str(raw_val)
                
                self._add_detail_row(f"{indent}{name}", val_str)
        
        except Exception as e:
            self.logger.debug(f"Error extracting fields: {e}")
    
    def _add_detail_row(self, param, value):
        """Add a row to details tree."""
        try:
            self.detail_tree.insert('', tk.END, values=(param, value))
        except Exception:
            pass
    
    def _start_update_loop(self):
        """Start periodic update loop."""
        self._update_active = True
        self._schedule_update()
    
    def _schedule_update(self):
        """Schedule next update."""
        if self._update_active:
            self._refresh_table()
            
            # If a message is selected, refresh its details
            if self._current_selected_label:
                self._update_details(self._current_selected_label)
            
            # Schedule next update (500ms)
            self.after(500, self._schedule_update)
    
    def stop(self):
        """Stop update loop."""
        self._update_active = False
