# 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.

from .circuit import Circuit, CircuitInstance, _CircuitBase, ParamRef


def format_param_value(value, param_name: str = None) -> str:
    """Format a parameter value for Spectre netlist output.

    - Strings are quoted (except for special parameters like 'probe')
    - Numbers are formatted appropriately
    - Other types are converted to string
    """
    if isinstance(value, ParamRef):
        return value.name
    if isinstance(value, str):
        # Special case: probe parameter is an instance reference, not a string
        # It should NOT be quoted
        if param_name == "probe":
            return value
        # String parameters need quotes in Spectre
        return f'"{value}"'
    elif isinstance(value, float):
        # Format floats reasonably
        return f"{value:g}"
    elif isinstance(value, int):
        return str(value)
    else:
        return str(value)


def is_builtin(circuit: Circuit) -> bool:
    """
    Check if a circuit is a built-in (primitive) device.

    Built-in devices have no internal structure - the simulator knows
    what they are (iprobe, resistor, vsource, etc.).

    User-defined circuits have internal instances and need subckt definitions.
    """
    return len(circuit.subcircuit_instances) == 0


def emit_instance(inst: CircuitInstance) -> str:
    """
    Emit an instance line (works for both built-in and user-defined circuits).

    Format: instance_name (net1 net2 ...) circuit_name param1=val1 ...
    """
    circuit = inst.subcircuit

    if hasattr(circuit, '_is_primitive') and circuit._is_primitive:
        if circuit._primitive_spectre_type == "vsource":
            # Extract connections (should be 'p' and 'n')
            p_net = inst.connections.get("p")
            n_net = inst.connections.get("n")
            if not p_net or not n_net:
                raise ValueError(f"Vsource primitive instance '{inst.name}' missing 'p' or 'n' connection.")

            # Retrieve the 'dc' parameter from the instance's parameters
            dc_value = inst.params.get("dc", 0.0)

            # Emit the Spectre vsource syntax
            return f"{inst.name} ({p_net.name} {n_net.name}) vsource dc={format_param_value(dc_value)}"
        else:
            # Generic primitive: use spectre_type as the model name
            # e.g., nmos, pmos, resistor, capacitor, etc.
            connections_list = [inst.connections[port].name for port in circuit.ports]
            connections = " ".join(connections_list)
            spectre_type = circuit._primitive_spectre_type

            if inst.params:
                param_str = " ".join(
                    f"{k}={format_param_value(v, k)}" for k, v in inst.params.items()
                )
                return f"{inst.name} ({connections}) {spectre_type} {param_str}"
            else:
                return f"{inst.name} ({connections}) {spectre_type}"
    else:
        # Connection order must match port order of circuit
        connections_list = [inst.connections[port].name for port in circuit.ports] # Ensure order based on circuit.ports
        connections = " ".join(connections_list)

        # Include parameter values if any
        if inst.params:
            param_str = " ".join(
                f"{k}={format_param_value(v, k)}" for k, v in inst.params.items()
            )
            return f"{inst.name} ({connections}) {circuit.name} {param_str}"
        else:
            return f"{inst.name} ({connections}) {circuit.name}"





def emit_subcircuit_definition(subckt: Circuit) -> str:
    """Emit a complete subcircuit definition block (maps to Spectre subckt)"""
    lines = []

    # Comments (for traceability of Python variable values)
    if subckt.comments:
        for comment in subckt.comments:
            lines.append(f"// {comment}")

    # Header: subckt name (port1 port2 ...)
    port_list = " ".join(subckt.ports)
    lines.append(f"subckt {subckt.name} ({port_list})")

    # Parameters line (if any)
    if subckt.parameters:
        param_list = " ".join(subckt.parameters)
        lines.append(f"    parameters {param_list}")

    # Body: all instances (unified - both built-in and nested subcircuits)
    for sub_inst in subckt.subcircuit_instances:
        lines.append(f"    {emit_instance(sub_inst)}")

    # Footer
    lines.append(f"ends {subckt.name}")

    return "\n".join(lines)


def collect_subcircuits(ckt: _CircuitBase, collected: dict = None) -> dict:
    """
    Recursively collect all user-defined circuit definitions needed.

    Only collects circuits that have internal structure (need subckt definition).
    Built-in devices are skipped (simulator knows them).

    Returns dict of {name: Circuit} to avoid duplicates.
    """
    if collected is None:
        collected = {}

    for sub_inst in ckt.subcircuit_instances:
        subckt = sub_inst.subcircuit

        # Only collect user-defined circuits (not built-ins)
        if not is_builtin(subckt) and subckt.name not in collected:
            # First, collect any nested subcircuits
            collect_subcircuits(subckt, collected)
            # Then add this one
            collected[subckt.name] = subckt

    return collected


def generate_spectre(ckt: _CircuitBase) -> str:
    """
    Generate a Spectre netlist from Circuit AST.

    Handles:
    - Built-in device instances (iprobe, resistor, etc.)
    - User-defined subcircuit definitions (recursive)
    - Subcircuit instances
    - Voltage sources
    - Testbench-specific: analyses, options, behavioral models
    """
    lines = []

    # Header comment
    lines.append(f"// Circuit: {ckt.name}")
    lines.append("// Generated by analogpy")
    lines.append("// n and p-type transistors terminals: drain gate source bulk")
    lines.append("")
    lines.append("simulator lang=spectre")

    # Check if this is a Testbench (has analyses, options, etc.)
    is_testbench = hasattr(ckt, 'analyses')

    # Global nets (if testbench)
    if is_testbench and hasattr(ckt, 'global_nets') and ckt.global_nets:
        global_str = " ".join(ckt.global_nets)
        lines.append(f"global {global_str}")

    lines.append("")

    # Behavioral models (Verilog-A includes)
    if is_testbench and hasattr(ckt, 'behavioral_models') and ckt.behavioral_models:
        lines.append("// Behavioral models")
        for model_path in ckt.behavioral_models:
            lines.append(f'ahdl_include "{model_path}"')
        lines.append("")

    # Collect and emit all subcircuit definitions first
    subcircuits = collect_subcircuits(ckt)
    if subcircuits:
        lines.append("// Subcircuit definitions")
        for subckt in subcircuits.values():
            lines.append(emit_subcircuit_definition(subckt))
            lines.append("")

    # Main circuit comment
    if subcircuits:
        lines.append("// Top-level circuit")

    # Emit all instances (unified - both built-in and subcircuits)
    for sub_inst in ckt.subcircuit_instances:
        lines.append(emit_instance(sub_inst))



    # Testbench-specific: simulator options and analyses
    if is_testbench:
        lines.append("")

        # Simulator options
        if hasattr(ckt, 'options'):
            lines.append("// Simulator options")
            lines.append(ckt.options.to_spectre())
            lines.append("")

        # Save options
        if hasattr(ckt, 'save_options'):
            lines.append(ckt.save_options.to_spectre())

        # Save statements
        if hasattr(ckt, 'get_all_saves'):
            all_saves = ckt.get_all_saves()
            if len(all_saves) > 0:
                lines.append("")
                lines.append("// Signal saves")
                lines.append(all_saves.to_spectre())

        # Analyses
        if ckt.analyses:
            lines.append("")
            lines.append("// Analyses")
            for analysis in ckt.analyses:
                lines.append(analysis.to_spectre())

    return "\n".join(lines)
