# Copyright 2026 Gaofeng Fan
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Configuration classes for schematic visualization."""

import os
import re
from dataclasses import dataclass, field
from enum import Enum
from typing import Dict, List, Optional, Tuple


# =============================================================================
# Centralized Color Configuration (Cadence-style)
# =============================================================================
# These colors are used globally across all symbol rendering for consistency.
# Update these values to change the color scheme for the entire visualization.

class SchematicColors:
    """
    Centralized color constants for schematic visualization.

    Color scheme follows Cadence Virtuoso conventions:
    - Green: Device symbols, cell names, signal wires
    - Red: Pin markers
    - Blue: Net labels, port names
    - Black: Pin names, parameter text
    - Orange: Instance names
    - Gray: Secondary labels (cell type, annotations)
    """
    # Primary symbol colors
    SYMBOL_OUTLINE = '#006600'      # Green - box outline, device body
    CELL_NAME = '#006600'           # Green - cell/model name inside symbol
    INSTANCE_NAME = '#FF6600'       # Orange - instance name outside symbol

    # Pin colors
    PIN_MARKER = '#CC0000'          # Red - filled square at pin location
    PIN_LABEL = '#000000'           # Black - pin name text (p, n, etc.)

    # Wire and net colors
    WIRE = '#0066CC'                # Blue - connection wires (same as net labels)
    NET_LABEL = '#0066CC'           # Blue - net name labels

    # Port colors (boundary pins)
    PORT_MARKER = '#CC0000'         # Red - port dots at boundary

    # Secondary labels
    CELL_TYPE = '#666666'           # Gray - type annotation (LUT, iprobe, etc.)
    PARAMETER_TEXT = '#666666'      # Gray - parameter values

    # Fill colors
    DEVICE_FILL = '#FFFFFF'         # White - device box fill
    PROBE_FILL = '#F8F8F8'          # Slightly off-white - probe devices

    # Background color for generated images
    BACKGROUND = '#FFFFFF'          # White background for contrast


# Convenience alias for importing
COLORS = SchematicColors


# =============================================================================
# Centralized Layout Configuration
# =============================================================================
# These parameters control spacing, sizing, and positioning of symbol elements.
# Adjust these values to fix overlap issues or change symbol appearance globally.

class SymbolLayout:
    """
    Centralized layout parameters for schematic symbols.

    All values are base units (will be multiplied by scale factor).
    Adjust these to fix overlap issues or change symbol spacing globally.
    """
    # Pin/terminal markers
    PIN_SIZE = 0.08               # Size of pin marker square
    LEAD_LENGTH = 0.3             # Length of lead wire from symbol body to pin

    # Text sizing
    CHAR_WIDTH = 0.10             # Approximate width per character for layout (was 0.08)
    LABEL_PADDING = 0.30          # Padding between pin marker and label

    # Box symbol dimensions
    BOX_MIN_WIDTH = 2.0           # Minimum box width
    BOX_MIN_HEIGHT = 1.2          # Minimum box height
    BOX_TEXT_PADDING = 0.6        # Extra padding for text inside box
    BOX_PORT_SPACING = 0.5        # Vertical spacing between ports

    # Label offsets (from box edge or symbol center)
    INSTANCE_LABEL_OFFSET = 1.35  # Instance name offset above box (was 0.25)
    INSTANCE_LABEL_POS = 'top-right'  # Global instance label position: 'top-right', 'top-left', 'above'
    MODEL_LABEL_OFFSET = 0.15     # Model name offset from center
    TYPE_LABEL_OFFSET = 0.25      # Type annotation offset below model (was 0.2)
    LABEL_X_OFFSET = 1.2          # Horizontal offset for labels beside symbols (was 0.8)
    LABEL_Y_SPACING = 0.35        # Vertical spacing between stacked labels (was 0.3)
    NET_LABEL_OFFSET = 0.8        # Distance from net label to wire/node (was 0.6)
    NET_LABEL_OFFSET_Y = -0.1      # Vertical offset for net labels (positive = up, negative = down)

    # Column spacing control for schematics
    MAX_LABEL_SPACING_CONTRIBUTION = 5.0  # Max label width contribution to column spacing (was 0.5)
    MAX_SYMBOL_WIDTH_FOR_SPACING = 3.0    # Max symbol width used for column spacing calculation (was 2.5)

    # Cell symbol dimensions
    CELL_MIN_WIDTH = 2.0          # Minimum cell symbol width
    CELL_MIN_HEIGHT = 1.5         # Minimum cell symbol height
    CELL_PORT_SPACING = 0.4       # Spacing between ports on cell symbol
    CELL_SEPARATOR_GAP = 0.3      # Gap between port type groups

    # Schematic layout dimensions
    SCHEMATIC_COL_SPACING = 4.5   # Column spacing in cell schematic (was 3.5)
    SCHEMATIC_ROW_SPACING = 4.0   # Row spacing in cell schematic (was 3.5)

    # Wire routing parameters
    WIRE_CHANNEL_WIDTH = 0.35     # Spacing between parallel wire channels
    WIRE_SYMBOL_MARGIN = 0.6      # Margin around symbols for wire routing
    WIRE_ROUTING_OFFSET = 0.5     # Additional offset for wire routing area

    # Schematic connection style
    # 'wires': Draw explicit wires between all same-net connections
    # 'labels': Show net name labels at all ports (implicit connection by name)
    # 'hybrid_clean': Smart routing - wires for close connections, labels for far ones
    SCHEMATIC_CONNECTION_STYLE = 'labels'

    # For hybrid mode: max column span before switching to label mode
    # If net spans more columns than this, use labels instead of wires
    HYBRID_MAX_COLUMN_SPAN = 1    # 0 = same column only, 1 = adjacent columns OK

    # Port layout in schematic view
    # 'top-bottom': Power at top, ground at bottom (traditional)
    # 'top-separated': Both power and ground at top, visually separated
    SCHEMATIC_PORT_LAYOUT = 'top-separated'

    # Component layout strategy
    # 'sequential': Place components in order they appear (default)
    # 'by-net': Group components that share nets together for shorter wires
    SCHEMATIC_COMPONENT_LAYOUT = 'sequential'  # Simple sequential layout

    # Source symbol dimensions
    SOURCE_RADIUS = 0.4           # Radius for circular source symbols

    # Transistor dimensions
    MOSFET_BODY_HEIGHT = 0.6      # MOSFET body height
    MOSFET_BODY_WIDTH = 0.3       # MOSFET body width

    # Per-symbol label adjustments (when default offset isn't enough)
    # These are ADDITIONAL offsets on top of LABEL_X_OFFSET
    CAPACITOR_LABEL_EXTRA_X = 0.25  # Extra x offset for capacitor labels
    CCCS_LABEL_EXTRA_X = 0.0        # Extra x offset for cccs labels (probe param)
    CCVS_LABEL_EXTRA_X = 0.0        # Extra x offset for ccvs labels (probe param)
    NMOS_LABEL_EXTRA_Y = 0.3        # Extra y offset for nmos labels (move up)
    PMOS_LABEL_EXTRA_Y = 0.3        # Extra y offset for pmos labels (move up)

    # Coordinate axis display (for schematic and symbol views)
    SHOW_AXES = False            # Show X/Y coordinate axes (frame style)
    #SHOW_AXES = True              # Show X/Y coordinate axes (frame style)
    AXIS_COLOR = '#444444'        # Dark gray for axis frame lines
    AXIS_TICK_SPACING = 2.0       # Spacing between tick marks
    AXIS_TICK_SIZE = 0.2          # Length of tick marks (pointing outward)
    AXIS_LABEL_COLOR = '#333333'  # Dark gray for axis labels (readable)
    AXIS_LABEL_FONTSIZE = 10      # Font size for axis tick labels
    AXIS_EXTEND = 0.5             # Extra extension beyond content bounds
    AXIS_MIRROR = True            # Draw mirrored axes (top/right edges)
    SHOW_GRID = True              # Show dotted grid lines
    GRID_COLOR = '#E0E0E0'        # Light gray for grid


# Convenience alias for importing
LAYOUT = SymbolLayout



# Parameter names that typically contain Verilog-A model file paths
VERILOGA_FILE_PARAMS = ['tmdata', 'tmCVdata', 'file', 'modelfile', 'datafile']


def extract_veriloga_filename(params: Dict) -> Optional[str]:
    """
    Extract Verilog-A filename from instance parameters.

    Looks for common parameter names that contain file paths
    (tmdata, tmCVdata, file, modelfile, datafile) and returns
    just the filename (without path).

    Args:
        params: Dictionary of instance parameters

    Returns:
        Filename string (e.g., "model.va") or None if not found
    """
    if not params:
        return None

    for param_name in VERILOGA_FILE_PARAMS:
        if param_name in params:
            filepath = str(params[param_name])
            # Extract just the filename, no path
            filename = os.path.basename(filepath)
            if filename:
                return filename

    return None


class SymbolStyle(Enum):
    """Symbol rendering styles."""
    MINIMAL = "minimal"      # Just model name in box
    STANDARD = "standard"    # Model name + port labels
    DETAILED = "detailed"    # Model name + ports + parameters


class PortType(Enum):
    """Port type for symbol placement."""
    POWER = "power"      # Top of symbol
    GROUND = "ground"    # Bottom of symbol
    INPUT = "input"      # Left side
    OUTPUT = "output"    # Right side
    INOUT = "inout"      # Left side (below inputs)
    UNKNOWN = "unknown"  # Left side (default)


# Patterns for inferring port type from name
PORT_TYPE_PATTERNS = {
    PortType.POWER: [
        # Standalone terminal names (2-terminal devices) - placed on TOP
        r'^p$',                   # p (positive terminal)
        r'^plus$',                # plus
        r'^pos$',                 # pos (positive)
        # Power supply names
        r'^v?dd[a-z0-9]*$',      # vdd, avdd, dvdd, vdda
        r'^v?cc[a-z0-9]*$',      # vcc, avcc, dvcc
        r'^supply[0-9]*$',       # supply, supply1
        r'^pwr[a-z0-9]*$',       # pwr, pwr1
        r'^anode$',              # anode (for diodes/OLEDs)
        r'.*_vdd$',              # xxx_vdd
        r'.*_pwr$',              # xxx_pwr
    ],
    PortType.GROUND: [
        # Standalone terminal names (2-terminal devices) - placed on BOTTOM
        r'^n$',                   # n (negative terminal)
        r'^m$',                   # m (minus, common in some conventions)
        r'^minus$',               # minus
        r'^neg$',                 # neg (negative)
        # Ground/reference names
        r'^v?ss[a-z0-9]*$',      # vss, avss, dvss, vssa
        r'^gnd[a-z0-9]*$',       # gnd, gnda, gnd1
        r'^ground$',             # ground
        r'^0$',                  # 0
        r'^elvss$',              # elvss (OLED ground)
        r'^cathode$',            # cathode
        r'.*_vss$',              # xxx_vss
        r'.*_gnd$',              # xxx_gnd
    ],
    PortType.OUTPUT: [
        # Differential outputs - outp/outn, out_p/out_n
        r'^out[a-z0-9_]*$',      # out, out1, outp, outn, out_p, out_n
        r'^q[a-z0-9]*$',         # q, qb, q0
        r'^y[0-9]*$',            # y, y0, y1
        r'^dout[a-z0-9]*$',      # dout, dout0
        r'.*_out$',              # xxx_out
        r'.*_o$',                # xxx_o
    ],
    PortType.INPUT: [
        # Note: inp/inn, in_p/in_n are matched by ^in[a-z0-9]*$ pattern
        r'^in[a-z0-9_]*$',       # in, in1, inp, inn, in_p, in_n, in_m
        r'^clk[a-z0-9]*$',       # clk, clk1, clkp
        r'^en[a-z0-9]*$',        # en, enable
        r'^rst[a-z0-9]*$',       # rst, reset, rstn
        r'^din[a-z0-9]*$',       # din, din0
        r'^a[0-9]+$',            # a0, a1 (address) - but NOT standalone 'a'
        r'^b[0-9]+$',            # b0, b1 - but NOT standalone 'b'
        r'^d[0-9]+$',            # d0, d1 (data) - but NOT standalone 'd'
        r'^sel[a-z0-9]*$',       # sel, sel0
        r'.*_in$',               # xxx_in
        r'.*_i$',                # xxx_i
    ],
    PortType.INOUT: [
        r'^io[a-z0-9]*$',        # io, io0
        r'^sda[0-9]*$',          # sda
        r'^scl[0-9]*$',          # scl
        r'^data[0-9]*$',         # data, data0
        r'^bus[a-z0-9]*$',       # bus, bus0
        r'.*_io$',               # xxx_io
    ],
}


def infer_port_type(port_name: str) -> PortType:
    """
    Infer port type from port name using naming conventions.

    Returns:
        PortType enum indicating where port should be placed on symbol
    """
    name_lower = port_name.lower()

    for port_type, patterns in PORT_TYPE_PATTERNS.items():
        for pattern in patterns:
            if re.match(pattern, name_lower):
                return port_type

    return PortType.UNKNOWN


def group_ports_by_type(ports: List[str],
                        overrides: Dict[str, PortType] = None) -> Dict[PortType, List[str]]:
    """
    Group ports by their inferred or overridden type.

    Args:
        ports: List of port names (order is preserved from cell definition)
        overrides: Optional dict of port_name -> PortType to override inference

    Returns:
        Dict mapping PortType to list of port names (preserving definition order)
    """
    overrides = overrides or {}
    grouped = {pt: [] for pt in PortType}

    # Preserve original definition order - just group by type
    for port in ports:
        if port in overrides:
            port_type = overrides[port]
        else:
            port_type = infer_port_type(port)
        grouped[port_type].append(port)

    return grouped


@dataclass
class SymbolConfig:
    """Configuration for a specific symbol."""
    style: SymbolStyle = SymbolStyle.DETAILED
    label: Optional[str] = None          # Custom label (overrides model name)
    color: str = "#E8E8E8"               # Fill color
    border_color: str = "#000000"        # Border color
    port_names: Optional[List[str]] = None  # Custom port names
    template: Optional[str] = None       # Use built-in template (e.g., "capacitor")
    width: float = 2.0                   # Symbol width in inches
    height: float = 1.0                  # Symbol height in inches
    is_custom: bool = False              # Whether this config came from a custom override


@dataclass
class LayoutHints:
    """Layout hints for positioning components."""
    positions: Dict[str, Tuple[float, float]] = field(default_factory=dict)
    rows: Dict[int, List[str]] = field(default_factory=dict)
    relative: Dict[str, Dict[str, str]] = field(default_factory=dict)

    def position(self, instance_name: str, x: float, y: float) -> 'LayoutHints':
        """Set absolute position for an instance."""
        self.positions[instance_name] = (x, y)
        return self

    def row(self, row_num: int, instances: List[str]) -> 'LayoutHints':
        """Place instances in a row."""
        self.rows[row_num] = instances
        return self

    def below(self, instance: str, reference: str) -> 'LayoutHints':
        """Place instance below reference."""
        if instance not in self.relative:
            self.relative[instance] = {}
        self.relative[instance]["below"] = reference
        return self

    def right_of(self, instance: str, reference: str) -> 'LayoutHints':
        """Place instance to the right of reference."""
        if instance not in self.relative:
            self.relative[instance] = {}
        self.relative[instance]["right_of"] = reference
        return self


@dataclass
class BookConfig:
    """Configuration for the entire schematic book."""
    title: str = "Schematic Documentation"
    author: str = ""
    default_style: SymbolStyle = SymbolStyle.DETAILED
    show_parameters: bool = True
    show_port_names: bool = True
    page_width: float = 11.0             # inches (Letter landscape)
    page_height: float = 8.5              # inches
    margin: float = 0.75                 # inches

    # Model-specific configurations
    model_configs: Dict[str, SymbolConfig] = field(default_factory=dict)

    # Layout configurations per circuit
    layout_hints: Dict[str, LayoutHints] = field(default_factory=dict)
