# 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 (tmux-ssh, 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 = "psfxl"
        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))
