####################################################################################################
#
# PySpice - A Spice Package for Python
# Copyright (C) 2017 Fabrice Salvaire
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
####################################################################################################
"""This module provides an interface to run xyce and get back the simulation
output.
"""
####################################################################################################
import logging
import os
import shutil
import subprocess
import tempfile
from PySpice.Config import ConfigInstall
from .RawFile import RawFile
####################################################################################################
_module_logger = logging.getLogger(__name__)
####################################################################################################
[docs]class XyceServer:
    """This class wraps the execution of Xyce and convert the output to a Python data structure.
    Example of usage::
      spice_server = XyceServer(xyce_command='/path/to/Xyce')
      raw_file = spice_server(spice_input)
    It returns a :obj:`PySpice.Spice.RawFile` instance.
    Default Xyce path is set in `XyceServer.XYCE_COMMAND`.
    """
    if ConfigInstall.OS.on_linux:
        XYCE_COMMAND = 'Xyce'
    elif ConfigInstall.OS.on_osx:
        XYCE_COMMAND = 'Xyce'
    elif ConfigInstall.OS.on_windows:
        XYCE_COMMAND = 'C:\\Program Files\\Xyce 6.10 OPENSOURCE\\bin\\Xyce.exe'
    else:
        raise NotImplementedError
    _logger = _module_logger.getChild('XyceServer')
    ##############################################
    def __init__(self, **kwargs):
        self._xyce_command = kwargs.get('xyce_command') or self.XYCE_COMMAND
    ##############################################
    def _parse_stdout(self, stdout):
        """Parse stdout for errors."""
        # log Spice output
        self._logger.info(os.linesep + stdout.decode('utf-8'))
        error_found = False
        simulation_failed = False
        warning_found = False
        lines = stdout.splitlines()
        for line_index, line in enumerate(lines):
            if line.startswith(b'Netlist warning'):
                warning_found = True
                # Fixme: highlight warnings
                self._logger.warning(os.linesep + line.decode('utf-8'))
            elif line.startswith(b'Netlist error'):
                error_found = True
                self._logger.error(os.linesep + line.decode('utf-8'))
            elif b'Transient failure history' in line:
                simulation_failed = True
                self._logger.error(os.linesep + line.decode('utf-8'))
        if error_found:
            raise NameError("Errors was found by Xyce")
        elif simulation_failed:
            raise NameError("Xyce simulation failed")
    ##############################################
    def __call__(self, spice_input):
        """Run SPICE in server mode as a subprocess for the given input and return a
        :obj:`PySpice.RawFile.RawFile` instance.
        """
        self._logger.debug('Start the xyce subprocess')
        tmp_dir = tempfile.mkdtemp()
        input_filename = os.path.join(tmp_dir, 'input.cir')
        output_filename = os.path.join(tmp_dir, 'output.raw')
        with open(input_filename, 'w') as f:
            f.write(str(spice_input))
        command = (self._xyce_command, '-r', output_filename, input_filename)
        self._logger.info('Run {}'.format(' '.join(command)))
        process = subprocess.Popen(
            command,
            stdin=subprocess.PIPE,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
        )
        stdout, stderr = process.communicate()
        self._parse_stdout(stdout)
        with open(output_filename, 'rb') as f:
            output = f.read()
        # self._logger.debug(output)
        raw_file = RawFile(output)
        shutil.rmtree(tmp_dir)
        return raw_file