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

"""Tests for ngspice netlist generation."""

from analogpy import (
    Circuit, Testbench, Net,
    nmos, pmos, resistor, capacitor, inductor,
    vsource, isource, vcvs, vccs, iprobe, diode, bjt, jfet,
    Transient, AC, DC, Noise,
    SaveConfig,
    generate_ngspice, generate_spectre,
)


# === Basic instance emission ===

def test_resistor():
    """Resistor emits r-prefix line with value."""
    tb = Testbench("test_r")
    a = tb.net("a")
    b = tb.net("b")
    tb.add_instance(resistor, "r1", p=a, n=b, r=1000)
    netlist = generate_ngspice(tb)
    assert "r1 a b 1000" in netlist


def test_capacitor():
    """Capacitor emits c-prefix line with value."""
    tb = Testbench("test_c")
    a = tb.net("a")
    b = tb.net("b")
    tb.add_instance(capacitor, "c1", p=a, n=b, c=1e-12)
    netlist = generate_ngspice(tb)
    assert "c1 a b 1e-12" in netlist


def test_inductor():
    """Inductor emits l-prefix line with value."""
    tb = Testbench("test_l")
    a = tb.net("a")
    b = tb.net("b")
    tb.add_instance(inductor, "l1", p=a, n=b, l=1e-9)
    netlist = generate_ngspice(tb)
    assert "l1 a b 1e-09" in netlist


def test_nmos_with_model():
    """NMOS emits m-prefix with model name and W/L parameters."""
    tb = Testbench("test_mos")
    d = tb.net("d")
    g = tb.net("g")
    s = tb.gnd()
    b = tb.gnd()
    tb.add_instance(nmos, "m1", d=d, g=g, s=s, b=b,
                    model="NMOS_3P3", w=1e-6, l=180e-9)
    netlist = generate_ngspice(tb)
    assert "m1 d g 0 0 NMOS_3P3 W=1e-06 L=1.8e-07" in netlist


def test_pmos_with_model():
    """PMOS emits m-prefix with model name."""
    tb = Testbench("test_pmos")
    d = tb.net("d")
    g = tb.net("g")
    s = tb.net("vdd")
    b = tb.net("vdd")
    tb.add_instance(pmos, "mp1", d=d, g=g, s=s, b=b,
                    model="PMOS_3P3", w=2e-6, l=180e-9)
    netlist = generate_ngspice(tb)
    assert "mp1 d g vdd vdd PMOS_3P3 W=2e-06 L=1.8e-07" in netlist


# === Voltage source variants ===

def test_vsource_dc():
    """DC voltage source emits simple v-prefix line."""
    tb = Testbench("test_vdc")
    p = tb.net("vdd")
    n = tb.gnd()
    tb.add_instance(vsource, "vdd", p=p, n=n, dc=1.8)
    netlist = generate_ngspice(tb)
    assert "vdd vdd 0 1.8" in netlist


def test_vsource_pulse():
    """Pulse source emits pulse() specification."""
    tb = Testbench("test_vpulse")
    p = tb.net("inp")
    n = tb.gnd()
    tb.add_instance(vsource, "vin", p=p, n=n, dc=0,
                    type="pulse", val0=0, val1=1.8,
                    delay=0, rise=1e-9, fall=1e-9,
                    width=5e-9, period=10e-9)
    netlist = generate_ngspice(tb)
    assert "vin inp 0 0 pulse (0 1.8 0 1e-09 1e-09 5e-09 1e-08)" in netlist


def test_vsource_sine():
    """Sine source emits sin() specification."""
    tb = Testbench("test_vsin")
    p = tb.net("inp")
    n = tb.gnd()
    tb.add_instance(vsource, "vin", p=p, n=n,
                    type="sine", vo=0, va=1.0, freq=1e6)
    netlist = generate_ngspice(tb)
    assert "vin inp 0 sin(0 1 1e+06 0 0)" in netlist


# === Current source ===

def test_isource():
    """Current source emits i-prefix line."""
    tb = Testbench("test_isrc")
    p = tb.net("a")
    n = tb.gnd()
    tb.add_instance(isource, "ibias", p=p, n=n, dc=100e-6)
    netlist = generate_ngspice(tb)
    assert "ibias a 0 0.0001" in netlist


# === Controlled sources ===

def test_vcvs():
    """VCVS emits e-prefix line with gain."""
    tb = Testbench("test_vcvs")
    tb.add_instance(vcvs, "e1",
                    p_out=tb.net("out_p"), n_out=tb.net("out_n"),
                    p_in=tb.net("in_p"), n_in=tb.net("in_n"),
                    gain=10)
    netlist = generate_ngspice(tb)
    assert "e1 out_p out_n in_p in_n 10" in netlist


def test_vccs():
    """VCCS emits g-prefix line with gm."""
    tb = Testbench("test_vccs")
    tb.add_instance(vccs, "g1",
                    p_out=tb.net("out_p"), n_out=tb.net("out_n"),
                    p_in=tb.net("in_p"), n_in=tb.net("in_n"),
                    gm=0.001)
    netlist = generate_ngspice(tb)
    assert "g1 out_p out_n in_p in_n 0.001" in netlist


# === Iprobe (zero-volt source) ===

def test_iprobe():
    """Iprobe emits as zero-volt voltage source."""
    tb = Testbench("test_iprobe")
    tb.add_instance(iprobe, "v_sense", p=tb.net("a"), n=tb.net("b"))
    netlist = generate_ngspice(tb)
    assert "v_sense a b 0" in netlist


# === Instance name prefix auto-correction ===

def test_auto_prefix_resistor():
    """Instance name gets r-prefix if missing."""
    tb = Testbench("test_prefix")
    tb.add_instance(resistor, "I_load", p=tb.net("a"), n=tb.net("b"), r=1000)
    netlist = generate_ngspice(tb)
    assert "rload a b 1000" in netlist


def test_auto_prefix_mosfet():
    """Instance name gets m-prefix if missing."""
    tb = Testbench("test_prefix_m")
    tb.add_instance(nmos, "I_M1", d=tb.net("d"), g=tb.net("g"),
                    s=tb.gnd(), b=tb.gnd(), model="N", w=1e-6, l=100e-9)
    netlist = generate_ngspice(tb)
    assert "mM1 d g 0 0 N" in netlist


def test_existing_prefix_kept():
    """Instance name already starting with correct prefix is kept as-is."""
    tb = Testbench("test_keep_prefix")
    tb.add_instance(resistor, "R_feedback", p=tb.net("a"), n=tb.net("b"), r=10000)
    netlist = generate_ngspice(tb)
    assert "R_feedback a b 10000" in netlist


# === Subcircuit definition and instantiation ===

def test_subcircuit():
    """Subcircuit emits .subckt/.ends block and X-prefix instance."""
    inv = Circuit(name="inverter", ports=["inp", "out", "vdd", "vss"])
    inv.add_instance(nmos, "MN", d=inv.net("out"), g=inv.net("inp"),
                     s=inv.net("vss"), b=inv.net("vss"), w=1e-6, l=180e-9)
    inv.add_instance(pmos, "MP", d=inv.net("out"), g=inv.net("inp"),
                     s=inv.net("vdd"), b=inv.net("vdd"), w=2e-6, l=180e-9)

    tb = Testbench("test_subckt")
    tb.add_instance(inv, "X1",
                    inp=tb.net("in"), out=tb.net("out"),
                    vdd=tb.net("vdd"), vss=tb.gnd())

    netlist = generate_ngspice(tb)
    assert ".subckt inverter inp out vdd vss" in netlist
    assert ".ends inverter" in netlist
    assert "X1 in out vdd 0 inverter" in netlist
    assert "MN out inp vss vss NMOS" in netlist
    assert "MP out inp vdd vdd PMOS" in netlist


# === Include directive ===

def test_include():
    """Testbench.include() emits .include in ngspice output."""
    tb = Testbench("test_include")
    tb.include("modelcard.CMOS90")
    tb.include("another_model.lib")
    netlist = generate_ngspice(tb)
    assert ".include modelcard.CMOS90" in netlist
    assert ".include another_model.lib" in netlist


def test_include_spectre():
    """Testbench.include() emits include directive in Spectre output."""
    tb = Testbench("test_include_spectre")
    tb.include("modelcard.CMOS90")
    netlist = generate_spectre(tb)
    assert 'include "modelcard.CMOS90"' in netlist


# === Analysis commands ===

def test_transient_analysis():
    """.tran emitted correctly."""
    tb = Testbench("test_tran")
    tb.add_analysis(Transient(stop=50, step=0.1))
    netlist = generate_ngspice(tb)
    assert ".tran 0.1 50" in netlist


def test_ac_analysis():
    """.ac emitted correctly."""
    tb = Testbench("test_ac")
    tb.add_analysis(AC(start=1, stop=1e9, points=100, variation="dec"))
    netlist = generate_ngspice(tb)
    assert ".ac dec 100 1 1000000000.0" in netlist


def test_dc_analysis():
    """.op emitted correctly."""
    tb = Testbench("test_dc")
    tb.add_analysis(DC())
    netlist = generate_ngspice(tb)
    assert ".op" in netlist


def test_noise_analysis():
    """.noise emitted correctly."""
    tb = Testbench("test_noise")
    tb.add_analysis(Noise(output="out", ref="0", input_src="VIN",
                          start=1, stop=1e9, points=100))
    netlist = generate_ngspice(tb)
    assert ".noise v(out,0) VIN dec 100 1 1000000000.0" in netlist


# === Save statements ===

def test_save_voltage():
    """SaveConfig.to_ngspice() emits .save v(...) lines."""
    tb = Testbench("test_save")
    saves = SaveConfig("debug").voltage("out", "vdd")
    tb.save(saves)
    netlist = generate_ngspice(tb)
    assert ".save v(out)" in netlist
    assert ".save v(vdd)" in netlist


# === .end termination ===

def test_end_termination():
    """Netlist ends with .end."""
    tb = Testbench("test_end")
    netlist = generate_ngspice(tb)
    assert netlist.strip().endswith(".end")


# === Title line ===

def test_title_first_line():
    """First line of netlist is the circuit name (ngspice title)."""
    tb = Testbench("my_circuit")
    netlist = generate_ngspice(tb)
    first_line = netlist.split("\n")[0]
    assert first_line == "my_circuit"


# === Complete demo circuit ===

def test_rlc_demo_circuit():
    """Full RLC demo circuit generates expected ngspice netlist."""
    tb = Testbench("rlc_circuit")
    tb.include("modelcard.CMOS90")

    vdd_net = tb.net("vdd")
    net_rc = tb.net("net_rc")
    gate = tb.net("gate")
    gnd = tb.gnd()

    tb.add_instance(resistor, "r", p=vdd_net, n=net_rc, r=100.0)
    tb.add_instance(inductor, "l", p=vdd_net, n=net_rc, l=1)
    tb.add_instance(capacitor, "c", p=vdd_net, n=net_rc, c=0.01)
    tb.add_instance(nmos, "m1", d=net_rc, g=gate, s=gnd, b=gnd,
                    model="N90", w=100e-6, l=0.09e-6)
    tb.add_instance(vsource, "vdd", p=vdd_net, n=gnd, dc=1.8)
    tb.add_instance(vsource, "vin", p=gate, n=gnd, dc=0,
                    type="pulse", val0=0, val1=1.8, delay=0,
                    rise=0.1, fall=0.1, width=15, period=30)

    tb.add_analysis(Transient(stop=50, step=0.1))

    netlist = generate_ngspice(tb)

    # Verify key components
    assert netlist.startswith("rlc_circuit")
    assert ".include modelcard.CMOS90" in netlist
    assert "r vdd net_rc 100" in netlist
    assert "l vdd net_rc 1" in netlist
    assert "c vdd net_rc 0.01" in netlist
    assert "m1 net_rc gate 0 0 N90 W=0.0001 L=9e-08" in netlist
    assert "vdd vdd 0 1.8" in netlist
    assert "vin gate 0 0 pulse (0 1.8 0 0.1 0.1 15 30)" in netlist
    assert ".tran 0.1 50" in netlist
    assert netlist.strip().endswith(".end")


# === Circuit (non-testbench) generation ===

def test_circuit_ngspice():
    """generate_ngspice works on plain Circuit (not Testbench)."""
    ckt = Circuit(name="simple", ports=["a", "b"])
    ckt.add_instance(resistor, "R1", p=ckt.net("a"), n=ckt.net("b"), r=100)
    netlist = generate_ngspice(ckt)
    assert netlist.startswith("simple")
    assert "R1 a b 100" in netlist
    assert netlist.strip().endswith(".end")


# === Diode ===

def test_diode():
    """Diode emits d-prefix line with model."""
    tb = Testbench("test_diode")
    tb.add_instance(diode, "d1", p=tb.net("a"), n=tb.gnd(), model="D1N4148")
    netlist = generate_ngspice(tb)
    assert "d1 a 0 D1N4148" in netlist


# === BJT ===

def test_bjt():
    """BJT emits q-prefix line with model."""
    tb = Testbench("test_bjt")
    tb.add_instance(bjt, "q1", c=tb.net("c"), b=tb.net("b"),
                    e=tb.gnd(), s=tb.gnd(), model="2N2222")
    netlist = generate_ngspice(tb)
    assert "q1 c b 0 0 2N2222" in netlist


# === JFET ===

def test_jfet():
    """JFET emits j-prefix line with model."""
    tb = Testbench("test_jfet")
    tb.add_instance(jfet, "j1", d=tb.net("d"), g=tb.net("g"),
                    s=tb.gnd(), b=tb.gnd(), model="J201")
    netlist = generate_ngspice(tb)
    assert "j1 d g 0 0 J201" in netlist
