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

"""
SpectreCommand builder for generating simulation commands.

This module generates spectre command lines but does NOT execute them.
Execution is left to external tools (tmux4ssh, subprocess, job schedulers).
"""

from typing import List, Optional
from pathlib import Path


class SpectreCommand:
    """
    Builds Spectre command line - does NOT execute.

    Example:
        cmd = (SpectreCommand("input.scs")
            .accuracy("liberal")
            .threads(16)
            .include_path("/path/to/models")
            .build())

        print(cmd)
        # spectre input.scs ++aps=liberal +mt=16 -I/path/to/models ...
    """

    def __init__(self, netlist_path: str):
        """
        Initialize with netlist path.

        Args:
            netlist_path: Path to the input netlist file
        """
        self.netlist = netlist_path
        self._format = "psfascii"
        self._raw_dir = None
        self._log_file = None
        self._include_paths: List[str] = []
        self._ahdl_libdir: Optional[str] = None
        self._flags: List[str] = []
        self._aps: Optional[str] = None
        self._aps_mode: str = '++'  # '++' for ++aps (default), '+' for +aps
        self._mt: Optional[int] = None
        self._lqtimeout: Optional[int] = None
        self._maxwarns: Optional[int] = None
        self._maxnotes: Optional[int] = None
        self._logstatus: bool = False

    def output_format(self, fmt: str) -> 'SpectreCommand':
        """Set output format (psfxl, psfbin, nutbin)."""
        self._format = fmt
        return self

    def raw_dir(self, path: str) -> 'SpectreCommand':
        """Set raw output directory."""
        self._raw_dir = path
        return self

    def log_file(self, path: str) -> 'SpectreCommand':
        """Set log file path."""
        self._log_file = path
        return self

    def include_path(self, *paths: str) -> 'SpectreCommand':
        """Add -I include paths for model files."""
        self._include_paths.extend(paths)
        return self

    def ahdl_libdir(self, path: str) -> 'SpectreCommand':
        """Set AHDL library directory."""
        self._ahdl_libdir = path
        return self

    def accuracy(self, level: str, mode: str = '++aps') -> 'SpectreCommand':
        """
        Set accuracy level and APS mode.

        Args:
            level: "conservative", "moderate", or "liberal"
            mode: Spectre acceleration mode:
                - "++aps" (default): Uses a different time-step control algorithm
                  for improved performance while satisfying error tolerances.
                  Emits: ++aps=<level>
                - "+aps": Enables Spectre APS mode, overrides errpreset.
                  Emits: +aps=<level>
                - "errpreset": Sets error tolerance preset only (base Spectre).
                  Emits: +errpreset=<level>
        """
        if level not in ("conservative", "moderate", "liberal"):
            raise ValueError(f"Invalid accuracy level: {level}")
        if mode not in ("++aps", "+aps", "errpreset"):
            raise ValueError(f"Invalid mode: {mode}. Use '++aps', '+aps', or 'errpreset'")
        self._aps = level
        if mode == '++aps':
            self._aps_mode = '++'
        elif mode == '+aps':
            self._aps_mode = '+'
        else:
            self._aps_mode = 'errpreset'
        return self

    def threads(self, n: int) -> 'SpectreCommand':
        """Set number of parallel threads."""
        self._mt = n
        return self

    def timeout(self, seconds: int) -> 'SpectreCommand':
        """Set license queue timeout in seconds (+lqtimeout)."""
        self._lqtimeout = seconds
        return self

    def max_warnings(self, n: int) -> 'SpectreCommand':
        """Set maximum warnings before abort (-maxw)."""
        self._maxwarns = n
        return self

    def max_notes(self, n: int) -> 'SpectreCommand':
        """Set maximum notes before suppression (-maxn)."""
        self._maxnotes = n
        return self

    def logstatus(self, enabled: bool = True) -> 'SpectreCommand':
        """Enable/disable +logstatus flag."""
        self._logstatus = enabled
        return self

    def flag(self, *flags: str) -> 'SpectreCommand':
        """Add custom flags."""
        self._flags.extend(flags)
        return self

    def build(self) -> str:
        """
        Build the complete spectre command string.

        Returns:
            Complete command string ready for execution
        """
        parts = ["spectre", self.netlist]

        # Flags
        parts.extend(self._flags)

        # Log and output
        if self._log_file:
            parts.append(f"+log {self._log_file}")
        if self._format:
            parts.append(f"-format {self._format}")
        if self._raw_dir:
            parts.append(f"-raw {self._raw_dir}")

        # Include paths
        for path in self._include_paths:
            parts.append(f"-I{path}")

        # AHDL library
        if self._ahdl_libdir:
            parts.append(f"-ahdllibdir {self._ahdl_libdir}")

        # Accuracy / APS mode
        if self._aps:
            if self._aps_mode == '++':
                parts.append(f"++aps={self._aps}")
            elif self._aps_mode == '+':
                parts.append(f"+aps={self._aps}")
            else:
                parts.append(f"+errpreset={self._aps}")

        # Threads
        if self._mt:
            parts.append(f"+mt={self._mt}")

        # Timeouts and limits
        if self._lqtimeout is not None:
            parts.append(f"+lqtimeout {self._lqtimeout}")
        if self._maxwarns is not None:
            parts.append(f"-maxw {self._maxwarns}")
        if self._maxnotes is not None:
            parts.append(f"-maxn {self._maxnotes}")

        # Log status
        if self._logstatus:
            parts.append("+logstatus")

        return " ".join(parts)

    def __str__(self) -> str:
        return self.build()


# =============================================================================
# Configuration presets
# =============================================================================

def spectre_command_liberal(netlist_path: str, threads: int = 16) -> SpectreCommand:
    """Create SpectreCommand with liberal (fast) settings."""
    return (SpectreCommand(netlist_path)
            .accuracy("liberal")
            .threads(threads))


def spectre_command_conservative(netlist_path: str, threads: int = 8) -> SpectreCommand:
    """Create SpectreCommand with conservative (robust) settings."""
    return (SpectreCommand(netlist_path)
            .accuracy("conservative")
            .threads(threads)
            .timeout(1800))


def spectre_command_moderate(netlist_path: str, threads: int = 16) -> SpectreCommand:
    """Create SpectreCommand with moderate settings."""
    return (SpectreCommand(netlist_path)
            .accuracy("moderate")
            .threads(threads))
