# SPDX-License-Identifier: AGPL-3.0-or-later | Commercial license available
# © Concepts 1996–2026 Miroslav Šotek. All rights reserved.
# © Code 2020–2026 Miroslav Šotek. All rights reserved.
# ORCID: 0009-0009-3560-0851
# Contact: www.anulum.li | protoscience@anulum.li
# SC-NeuroCore — Verilog export for LIF-based networks

"""Verilog export for LIF-based networks targeting iCE40/generic FPGA."""

from __future__ import annotations

import os

from sc_neurocore.exceptions import SCHardwareError

_LIF_MODELS = frozenset(
    {
        "LapicqueNeuron",
        "FixedPointLIFNeuron",
        "StochasticLIFNeuron",
        "HomeostaticLIFNeuron",
        "SpiNNakerLIFNeuron",
        "LoihiCUBANeuron",
        "COBALIFNeuron",
        "ComplementaryLIFNeuron",
        "NonResettingLIFNeuron",
        "ParametricLIFNeuron",
        "InhibitoryLIFNeuron",
        "TwoCompartmentLIFNeuron",
        "GatedLIFNeuron",
        "KLIFNeuron",
        "NonlinearLIFNeuron",
        "FractionalLIFNeuron",
        "EnergyLIFNeuron",
    }
)


def _check_exportable(network):
    """Validate all populations use LIF-compatible models."""
    for pop in network.populations:
        name = pop._model_cls.__name__
        if name not in _LIF_MODELS:
            raise SCHardwareError(
                f"Model '{name}' cannot be exported to Verilog. Supported: {sorted(_LIF_MODELS)}"
            )


def _emit_lif_module(pop, idx):
    """Generate Verilog for one LIF population."""
    n = pop.n
    sample = pop.neurons[0]
    v_thresh = int(getattr(sample, "v_threshold", 1) * 256)
    v_reset = int(getattr(sample, "v_reset", 0) * 256)
    tau = int(getattr(sample, "tau", 20))
    return (
        f"// Population {idx}: {pop.label} ({n} neurons)\n"
        f"sc_lif_array #(\n"
        f"    .N({n}),\n"
        f"    .V_THRESH({v_thresh}),\n"
        f"    .V_RESET({v_reset}),\n"
        f"    .TAU({tau})\n"
        f") pop_{idx} (\n"
        f"    .clk(clk),\n"
        f"    .rst_n(rst_n),\n"
        f"    .current_in(current_{idx}),\n"
        f"    .spike_out(spike_{idx})\n"
        f");\n"
    )


def _emit_top(network, target):
    """Emit top-level Verilog wrapper."""
    n_pops = len(network.populations)
    lines = [
        f"// Auto-generated by SC-NeuroCore — target: {target}\n",
        "module sc_network_top (\n",
        "    input wire clk,\n",
        "    input wire rst_n,\n",
    ]
    for i, pop in enumerate(network.populations):
        lines.append(f"    input wire [{pop.n * 8 - 1}:0] current_{i},\n")
    for i, pop in enumerate(network.populations):
        comma = "" if i == n_pops - 1 else ","
        lines.append(f"    output wire [{pop.n - 1}:0] spike_{i}{comma}\n")
    lines.append(");\n\n")
    for i, pop in enumerate(network.populations):
        lines.append(_emit_lif_module(pop, i))
        lines.append("\n")
    lines.append("endmodule\n")
    return "".join(lines)


def export_verilog(network, output_dir, target="ice40"):
    """Export a LIF-based network to Verilog files."""
    _check_exportable(network)
    os.makedirs(output_dir, exist_ok=True)
    top_v = _emit_top(network, target)
    top_path = os.path.join(output_dir, "sc_network_top.v")
    with open(top_path, "w") as f:
        f.write(top_v)
    params_path = os.path.join(output_dir, "params.vh")
    with open(params_path, "w") as f:
        f.write(f"// SC-NeuroCore network parameters — target: {target}\n")
        for i, pop in enumerate(network.populations):
            f.write(f"`define POP_{i}_SIZE {pop.n}\n")
    return top_path
