####################################################################################################
#
# PySpice - A Spice Package for Python
# Copyright (C) 2014 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 implements SPICE circuit elements.

.. warning:: Some elements are partially implemented.

.. warning:: The API documentation generated by Sphinx is perfectible for this module. The source
             code can be more informative.

.. note:: It would be nice to have a useful and working documentation in the interactive environment.

The element's parameters are internally implemented using class attributes and descriptors.

There are two types of parameters, positionals which are required and where the order mater, and
optional parameters which are passed as keyword arguments.

Parameters are registered with more expressive and longer names than their Spice counterparts.  For
optional parameters, we can use both Spice and longer name for convenience.

See Ngspice documentation for details.

+--------------+------------------------------------------------------+
| First letter + Element description                                  |
+--------------+------------------------------------------------------+
| A            + XSPICE code model                                    |
+--------------+------------------------------------------------------+
| B            + Behavioral (arbitrary) source                        |
+--------------+------------------------------------------------------+
| C            + Capacitor                                            |
+--------------+------------------------------------------------------+
| D            + Diode                                                |
+--------------+------------------------------------------------------+
| E            + Voltage-controlled voltage source (VCVS)             |
+--------------+------------------------------------------------------+
| F            + Current-controlled current source (CCCs)             |
+--------------+------------------------------------------------------+
| G            + Voltage-controlled current source (VCCS)             |
+--------------+------------------------------------------------------+
| H            + Current-controlled voltage source (CCVS)             |
+--------------+------------------------------------------------------+
| I            + Current source                                       |
+--------------+------------------------------------------------------+
| J            + Junction field effect transistor (JFET)              |
+--------------+------------------------------------------------------+
| K            + Coupled (Mutual) Inductors                           |
+--------------+------------------------------------------------------+
| L            + Inductor                                             |
+--------------+------------------------------------------------------+
| M            + Metal oxide field effect transistor (MOSFET)         |
+--------------+------------------------------------------------------+
| N            + Numerical device for GSS                             |
+--------------+------------------------------------------------------+
| O            + Lossy transmission line                              |
+--------------+------------------------------------------------------+
| P            + Coupled multiconductor line (CPL)                    |
+--------------+------------------------------------------------------+
| Q            + Bipolar junction transistor (BJT)                    |
+--------------+------------------------------------------------------+
| R            + Resistor                                             |
+--------------+------------------------------------------------------+
| S            + Switch (voltage-controlled)                          |
+--------------+------------------------------------------------------+
| T            + Lossless transmission line                           |
+--------------+------------------------------------------------------+
| U            + Uniformly distributed RC line                        |
+--------------+------------------------------------------------------+
| V            + Voltage source                                       |
+--------------+------------------------------------------------------+
| W            + Switch (current-controlled)                          |
+--------------+------------------------------------------------------+
| X            + Subcircuit                                           |
+--------------+------------------------------------------------------+
| Y            + Single lossy transmission line (TXL)                 |
+--------------+------------------------------------------------------+
| Z            + Metal semiconductor field effect transistor (MESFET) |
+--------------+------------------------------------------------------+

"""

####################################################################################################

import logging

from ..Tools.StringTools import str_spice, join_list, join_dict
from ..Unit import U_m, U_s, U_A, U_V, U_Degree, U_Ω, U_F, U_H, U_Hz
from .Netlist import (Element, AnyPinElement, FixedPinElement, NPinElement, OptionalPin)
from .ElementParameter import (
    # KeyValueParameter,
    BoolKeyParameter,
    ElementNamePositionalParameter,
    ExpressionKeyParameter,
    ExpressionPositionalParameter,
    FlagParameter,
    FloatKeyParameter,
    FloatPairKeyParameter,
    FloatTripletKeyParameter,
    FloatPositionalParameter,
    InitialStatePositionalParameter,
    IntKeyParameter,
    ModelPositionalParameter,
    )

####################################################################################################

_module_logger = logging.getLogger(__name__)

####################################################################################################

class DipoleElement(FixedPinElement):
    """This class implements a base class for dipole element."""
    PINS = ('plus', 'minus')

class TwoPortElement(FixedPinElement):
    """This class implements a base class for two-port element."""
    PINS = ('output_plus', 'output_minus', 'input_plus', 'input_minus')

####################################################################################################

class SubCircuitElement(NPinElement):

    """This class implements a sub-circuit.

    Spice syntax:

    .. code-block:: none

        XYYYYYY node1 node2 ... subcircuit_name parameter1=value1 ...

    Attributes:

      :attr:`subcircuit_name`

    .. note:: As opposite to Spice, the circuit's name is specified before the nodes so as to act as `*args`.

    """

    ALIAS = 'X'
    PREFIX = 'X'

    subcircuit_name = ElementNamePositionalParameter(position=0, key_parameter=False)

    ##############################################

    def __init__(self, netlist, name, subcircuit_name, *nodes, **parameters):

        super().__init__(netlist, name, nodes, subcircuit_name)

        # Fixme: match parameters to subcircuit
        self.parameters = parameters

        # Fixme: investigate
        # for key, value in parameters.items():
        #     parameter = KeyValueParameter(key)
        #     parameter.__set__(self, value)
        #     self.optional_parameters[key] = parameter
        #     setattr(self, key, parameter)

    ##############################################

    def copy_to(self, netlist):

        element = self.__class__(netlist, self._name, self.subcircuit_name, *self.node_names, **self.parameters)
        # Element.copy_to(self, element)
        return element

    ##############################################

    def format_spice_parameters(self):
        """ Return the formatted list of parameters. """

        spice_parameters = super().format_spice_parameters()
        if self.parameters:
            spice_parameters += ' ' + join_dict(self.parameters)

        return spice_parameters

####################################################################################################
#
# Elementary devices: Resistor, Capacitor, Inductor, Switch (VCSw/CCSw)
#
####################################################################################################

class Resistor(DipoleElement):

    """This class implements a resistor.

    Spice syntax:

    .. code-block:: none

        RXXXXXXX n+ n- value <ac=val> <m=val> <scale=val> <temp=val> <dtemp=val> <noisy=0|1>

    Keyword Parameters:

      :attr:`ac`

      :attr:`multiplier`
         alias `m`

      :attr:`scale`

      :attr:`temperature`
         alias `temp`

      :attr:`device_temperature`
         alias `dtemp`

      :attr:`noisy`

    Attributes:

      :attr:`resistance`

      :attr:`ac`

      :attr:`multiplier`

      :attr:`scale`

      :attr:`temperature`

      :attr:`device_temperature`

      :attr:`noisy`

    """

    ALIAS = 'R'
    PREFIX = 'R'

    resistance = FloatPositionalParameter(position=0, key_parameter=False, unit=U_Ω)
    ac = FloatKeyParameter('ac', unit=U_Ω)
    multiplier = IntKeyParameter('m')
    scale = FloatKeyParameter('scale')
    temperature = FloatKeyParameter('temp', unit=U_Degree)
    device_temperature = FloatKeyParameter('dtemp', unit=U_Degree)
    noisy = BoolKeyParameter('noisy')

####################################################################################################

class SemiconductorResistor(DipoleElement):

    """This class implements a Semiconductor resistor.

    Spice syntax:

    .. code-block:: none

        RXXXXXXX n+ n- <value> <mname> <l=length> <w=width> <temp=val> <dtemp=val> m=<val> <ac=val> <scale=val> <noisy=0|1>

    Keyword Parameters:

      :attr:`model`

      :attr:`length`
         alias `l`

      :attr:`width`
         alias `w`

      :attr:`temperature`
         alias `temp`

      :attr:`device_temperature`
         alias `dtemp`

      :attr:`multiplier`
         alias `m`

      :attr:`ac`

      :attr:`scale`

      :attr:`noisy`

    Attributes:

      :attr:`resistance`

      :attr:`model`

      :attr:`length`

      :attr:`width`

      :attr:`temperature`

      :attr:`device_temperature`

      :attr:`multiplier`

      :attr:`ac`

      :attr:`scale`

      :attr:`noisy`

    """

    ALIAS = 'SemiconductorResistor'
    PREFIX = 'R'

    resistance = FloatPositionalParameter(position=0, key_parameter=False, unit=U_Ω)
    model = ModelPositionalParameter(position=1, key_parameter=True)
    length = FloatKeyParameter('l', unit=U_m)
    width = FloatKeyParameter('w', unit=U_m)
    temperature = FloatKeyParameter('temp', unit=U_Degree)
    device_temperature = FloatKeyParameter('dtemp', unit=U_Degree)
    multiplier = IntKeyParameter('m')
    ac = FloatKeyParameter('ac', unit=U_Ω)
    scale = FloatKeyParameter('scale')
    noisy = BoolKeyParameter('noisy')

####################################################################################################

class BehavioralResistor(DipoleElement):

    # Behavioral / Behavioural

    """This class implements a behavioral resistor.

    Spice syntax:

    .. code-block:: none

        RXXXXXXX n+ n- 'expression' <tc1=value> <tc2=value>
        Rxxxxxxx n+ n- R='expression' <tc1=value> <tc2=value>

    Keyword Parameters:

      :attr:`tc1`

      :attr:`tc2`

    Attributes:

      :attr:`resistance_expression`

      :attr:`tc1`

      :attr:`tc2`

    """

    ALIAS = 'BehavioralResistor'
    PREFIX = 'R'

    resistance_expression = ExpressionPositionalParameter(position=0, key_parameter=False)
    tc1 = FloatKeyParameter('tc1')
    tc2 = FloatKeyParameter('tc2')

####################################################################################################

class Capacitor(DipoleElement):

    """This class implements a capacitor.

    Spice syntax:

    .. code-block:: none

        CXXXXXXX n+ n- <value> <mname> <m=val> <scale=val> <temp=val> <dtemp=val> <ic=init_condition>

    Keyword Parameters:

      :attr:`model`

      :attr:`multiplier`
         alias `m`

      :attr:`scale`

      :attr:`temperature`
         alias `temp`

      :attr:`device_temperature`
         alias `dtemp`

      :attr:`initial_condition`
         alias `ic`

    Attributes:

      :attr:`capacitance`

      :attr:`model`

      :attr:`multiplier`

      :attr:`scale`

      :attr:`temperature`

      :attr:`device_temperature`

      :attr:`initial_condition`

    """

    ALIAS = 'C'
    PREFIX = 'C'

    capacitance = FloatPositionalParameter(position=0, key_parameter=False, unit=U_F)
    model = ModelPositionalParameter(position=1, key_parameter=True)
    multiplier = IntKeyParameter('m')
    scale = FloatKeyParameter('scale')
    temperature = FloatKeyParameter('temp', unit=U_Degree)
    device_temperature = FloatKeyParameter('dtemp', unit=U_Degree)
    initial_condition = FloatKeyParameter('ic')

####################################################################################################

class SemiconductorCapacitor(DipoleElement):

    """This class implements a semiconductor capacitor.

    Spice syntax:

    .. code-block:: none

        CXXXXXXX n+ n- <value> <mname> <l=length> <w=width> <m=val> <scale=val> <temp=val> <dtemp=val> <ic=init_condition>

    Keyword Parameters:

      :attr:`model`

      :attr:`length`
         alias `l`

      :attr:`width`
         alias `w`

      :attr:`multiplier`
         alias `m`

      :attr:`scale`

      :attr:`temperature`
         alias `temp`

      :attr:`device_temperature`
         alias `dtemp`

      :attr:`initial_condition`
         alias `ic`

    Attributes:

      :attr:`capacitance`

      :attr:`model`

      :attr:`length`

      :attr:`width`

      :attr:`multiplier`

      :attr:`scale`

      :attr:`temperature`

      :attr:`device_temperature`

      :attr:`initial_condition`

    """

    ALIAS = 'SemiconductorCapacitor'
    PREFIX = 'C'

    capacitance = FloatPositionalParameter(position=0, key_parameter=False, unit=U_F)
    model = ModelPositionalParameter(position=1, key_parameter=True)
    length = FloatKeyParameter('l', unit=U_m)
    width = FloatKeyParameter('w', unit=U_m)
    multiplier = IntKeyParameter('m')
    scale = FloatKeyParameter('scale')
    temperature = FloatKeyParameter('temp', unit=U_Degree)
    device_temperature = FloatKeyParameter('dtemp', unit=U_Degree)
    initial_condition = FloatKeyParameter('ic')

####################################################################################################

class BehavioralCapacitor(DipoleElement):

    """This class implements a behavioral capacitor.

    Spice syntax:

    .. code-block:: none

        CXXXXXXX n+ n- 'expression' <tc1=value> <tc2=value>
        CXXXXXXX n+ n- C='expression' <tc1=value> <tc2=value>

    Keyword Parameters:

      :attr:`tc1`

      :attr:`tc2`

    Attributes:

      :attr:`capacitance_expression`

      :attr:`tc1`

      :attr:`tc2`

    """

    ALIAS = 'BehavioralCapacitor'
    PREFIX = 'C'

    capacitance_expression = ExpressionPositionalParameter(position=0, key_parameter=False)
    tc1 = FloatKeyParameter('tc1')
    tc2 = FloatKeyParameter('tc2')

####################################################################################################

class Inductor(DipoleElement):

    """This class implements an inductor.

    Spice syntax:

    .. code-block:: none

        LYYYYYYY n+ n- <value> <mname> <nt=val> <m=val> <scale=val> <temp=val> <dtemp=val> <ic=init_condition>

    Keyword Parameters:

      :attr:`nt`

      :attr:`multiplier`
         alias `m`

      :attr:`scale`

      :attr:`temperature`
         alias `temp`

      :attr:`device_temperature`
         alias `dtemp`

      :attr:`initial_condition`
         alias `ic`

    Attributes:

      :attr:`inductance`

      :attr:`model`

      :attr:`nt`

      :attr:`multiplier`

      :attr:`scale`

      :attr:`temperature`

      :attr:`device_temperature`

      :attr:`initial_condition`

    """

    ALIAS = 'L'
    PREFIX = 'L'

    inductance = FloatPositionalParameter(position=0, key_parameter=False, unit=U_H)
    model = ModelPositionalParameter(position=1, key_parameter=True)
    nt = FloatKeyParameter('nt')
    multiplier = IntKeyParameter('m')
    scale = FloatKeyParameter('scale')
    temperature = FloatKeyParameter('temp', unit=U_Degree)
    device_temperature = FloatKeyParameter('dtemp', unit=U_Degree)
    initial_condition = FloatKeyParameter('ic')

####################################################################################################

class BehavioralInductor(DipoleElement):

    """This class implements a behavioral inductor.

    Spice syntax:

    .. code-block:: none

        LXXXXXXX n+ n- 'expression' <tc1=value> <tc2=value>
        LXXXXXXX n+ n- L='expression' <tc1=value> <tc2=value>

    Keyword Parameters:

      :attr:`tc1`

      :attr:`tc2`

    Attributes:

      :attr:`inductance_expression`

      :attr:`tc1`

      :attr:`tc2`

    """

    ALIAS = 'BehavioralInductor'
    PREFIX = 'L'

    inductance_expression = ExpressionPositionalParameter(position=0, key_parameter=False)
    tc1 = FloatKeyParameter('tc1')
    tc2 = FloatKeyParameter('tc2')

####################################################################################################

class CoupledInductor(AnyPinElement):

    """This class implementss a coupled (mutual) inductors.

    Spice syntax:

    .. code-block:: none

        KXXXXXXX LYYYYYYY LZZZZZZZ value

    Keyword Parameters:

    Attributes:

      :attr:`inductor1`

      :attr:`inductor2`

      :attr:`coupling_factor`

    """

    ALIAS = 'K'
    PREFIX = 'K'

    inductor1 = ElementNamePositionalParameter(position=0, key_parameter=False)
    inductor2 = ElementNamePositionalParameter(position=1, key_parameter=False)
    coupling_factor = FloatPositionalParameter(position=2, key_parameter=False)

    _logger = _module_logger.getChild('CoupledInductor')

 ##############################################

    def __init__(self, name, *args, **kwargs):

        super().__init__(name, *args, **kwargs)

        self._inductors = []
        for inductor in (self.inductor1, self.inductor2):
            try:
                self.netlist.element(inductor)
            except KeyError:
                try:
                    inductor = 'L' + inductor
                    self.netlist.element(inductor)
                    self._logger.info('Prefixed element {}'.format(inductor))
                except KeyError:
                    raise ValueError('Element with name {} not found'.format(inductor))
            # Fixme: str or Element instance ?
            self._inductors.append(inductor)
        self.inductor1, self.inductor2 = self._inductors

####################################################################################################

class VoltageControlledSwitch(TwoPortElement):

    """This class implements a voltage controlled switch.

    Spice syntax:

    .. code-block:: none

        SXXXXXXX n+ n- nc+ nc- model <on> <off>

    Keyword Parameters:

      :attr:`model`

      :attr:`initial_state`

    Attributes:

      :attr:`model`

      :attr:`initial_state`

    """

    ALIAS = 'S'
    LONG_ALIAS = 'VCS'
    PREFIX = 'S'

    model = ModelPositionalParameter(position=0, key_parameter=True)
    initial_state = InitialStatePositionalParameter(position=1, key_parameter=True)

####################################################################################################

class CurrentControlledSwitch(DipoleElement):

    """This class implements a current controlled switch.

    Spice syntax:

    .. code-block:: none

        WYYYYYYY n+ n- vname model <on> <off>

    Keyword Parameters:

      :attr:`source`

      :attr:`model`

      :attr:`initial_state`

    Attributes:

      :attr:`source`

      :attr:`model`

      :attr:`initial_state`

    """

    ALIAS = 'W'
    LONG_ALIAS = 'CCS'
    PREFIX = 'W'

    source = ElementNamePositionalParameter(position=0, key_parameter=True)
    model = ModelPositionalParameter(position=1, key_parameter=True)
    initial_state = InitialStatePositionalParameter(position=2, key_parameter=True)

####################################################################################################
#
# Voltage and Current Sources
#
####################################################################################################

class VoltageSource(DipoleElement):

    """This class implements an independent sources for voltage.

    Spice syntax:

    .. code-block:: none

        VXXXXXXX n+ n- <<dc> dc/tran value> <ac <acmag <acphase>>> <distof1 <f1mag <f1phase>>> <distof2 <f2mag <f2phase>>>

    Keyword Parameters:

    Attributes:

      :attr:`dc_value`

    """

    ALIAS = 'V'
    PREFIX = 'V'

    # Fixme: ngspice manual doesn't describe well the syntax
    dc_value = FloatPositionalParameter(position=0, key_parameter=False, unit=U_V)

####################################################################################################

class CurrentSource(DipoleElement):

    """This class implements an independent sources for current.

    Spice syntax:

    .. code-block:: none

       IYYYYYYY n+ n- <<dc> dc/tran value> <ac <acmag <acphase>>> <distof1 <f1mag <f1phase>>> <distof2 <f2mag <f2phase>>>

    Keyword Parameters:

    Attributes:

      :attr:`dc_value`

    """

    ALIAS = 'I'
    PREFIX = 'I'

    # Fixme: ngspice manual doesn't describe well the syntax
    dc_value = FloatPositionalParameter(position=0, key_parameter=False, unit=U_A)

####################################################################################################

class VoltageControlledCurrentSource(TwoPortElement):

    """This class implements a linear voltage-controlled current sources (VCCS).

    Spice syntax:

    .. code-block:: none

        Gxxx n+ n- nc+ nc- value <m=val>

    Keyword Parameters:

      :attr:`multiplier`
         alias `m`

    Attributes:

      :attr:`transconductance`

    """

    ALIAS = 'VCCS'
    PREFIX = 'G'

    transconductance = ExpressionPositionalParameter(position=0, key_parameter=False)
    multiplier = IntKeyParameter('m')

####################################################################################################

class VoltageControlledVoltageSource(TwoPortElement):

    """This class implements a linear voltage-controlled voltage sources (VCVS).

    Spice syntax:

    .. code-block:: none

        EXXXXXXX n+ n- nc+ nc- value

    Keyword Parameters:

    Attributes:

      :attr:`voltage_gain`

    """

    ALIAS = 'VCVS'
    PREFIX = 'E'

    voltage_gain = ExpressionPositionalParameter(position=0, key_parameter=False)

####################################################################################################

class CurrentControlledCurrentSource(DipoleElement):

    """This class implements a linear current-controlled current sources (CCCS).

    Spice syntax:

    .. code-block:: none

       FXXXXXXX n+ n- vname value <m=val>

    Keyword Parameters:

      :attr:`multiplier`
         alias `m`

    Attributes:

      :attr:`source`

      :attr:`current_gain`

    """

    ALIAS = 'F'
    LONG_ALIAS = 'CCCS'
    PREFIX = 'F'

    source = ElementNamePositionalParameter(position=0, key_parameter=False)
    current_gain = ExpressionPositionalParameter(position=1, key_parameter=False)
    multiplier = IntKeyParameter('m')

####################################################################################################

class CurrentControlledVoltageSource(DipoleElement):

    """This class implements a linear current-controlled voltage sources (CCVS).

    Spice syntax:

    .. code-block:: none

        HXXXXXXX n+ n- vname value

    Keyword Parameters:

    Attributes:

      :attr:`source`

      :attr:`transresistance`

    """

    ALIAS = 'H'
    LONG_ALIAS = 'CCVS'
    PREFIX = 'H'

    source = ElementNamePositionalParameter(position=0, key_parameter=False)
    transresistance = ExpressionPositionalParameter(position=1, key_parameter=False)

####################################################################################################
#
# Non-Linear Dependent Sources (Behavioral Sources)
#
####################################################################################################

class BehavioralSource(DipoleElement):

    """This class implements a behavioral source.

    Spice syntax:

    .. code-block:: none

        BXXXXXXX n+ n- <i=expr> <v=expr> <tc1=value> <tc2=value> <temp=value> <dtemp=value>

    Keyword Parameters:

      :attr:`current_expression`
          alias `i`

      :attr:`voltage_expression`
          alias `v`

      :attr:`tc1`

      :attr:`tc2`

      :attr:`temperature`
         alias `temp`

      :attr:`device_temperature`
         alias `dtemp`

    Attributes:

      :attr:`current_expression`

      :attr:`voltage_expression`

      :attr:`tc1`

      :attr:`tc2`

      :attr:`temperature`

      :attr:`device_temperature`

    """

    ALIAS = 'B'
    PREFIX = 'B'

    current_expression = ExpressionKeyParameter('i')
    voltage_expression = ExpressionKeyParameter('v')
    tc1 = FloatKeyParameter('tc1')
    tc2 = FloatKeyParameter('tc2')
    temperature = FloatKeyParameter('temp', unit=U_Degree)
    device_temperature = FloatKeyParameter('dtemp', unit=U_Degree)

####################################################################################################

class NonLinearVoltageSource(DipoleElement):

    """This class implements a non-linear voltage source.

    .. warning:: Partially implemented

    Spice syntax:

    .. code-block:: none

        Exxx n+ n- vol='expr'
        Exxx n+ n- value={expr}
        Exxx n1 n2 TABLE {expression}=(x0,y0) (x1,y1) (x2,y2)
        Exxx n+ n- ( POLY (nd) ) nc1+ nc1- ( nc2+ nc2- ... ) p0 ( p1 ... )
        Laplace

    Keyword Parameters:

    Attributes:

    """

    ALIAS = 'NonLinearVoltageSource'
    PREFIX = 'E'

    # Fixme:
    VALID_KWARGS = ('expression', 'table')

    ##############################################

    def __init__(self, name, *args, **kwargs):

        super().__init__(name, *args, **kwargs)

        self.expression = kwargs.get('expression', None)
        self.table = kwargs.get('table', None)

    ##############################################

    def __str__(self):

        spice_element = self.format_node_names()
        # Fixme: expression
        if self.table is not None:
            # TABLE {expression} = (x0, y0) (x1, y1) ...
            table = ['({}, {})'.format(str_spice(x), str_spice(y)) for x, y in self.table]
            spice_element += ' TABLE {%s} = %s' % (self.expression, join_list(table))
        return spice_element

####################################################################################################

class NonLinearCurrentSource(DipoleElement):

    """This class implements a non-linear current sources.

    .. warning:: Partially implemented

    Spice syntax:

    .. code-block:: none

        Gxxx n+ n- value={expr}
        Gxxx n1 n2 TABLE {expression}=(x0,y0) (x1,y1) (x2,y2)
        Gxxx n+ n- ( POLY (nd) ) nc1+ nc1- ( nc2+ nc2- ... ) p0 ( p1 ... )
        Laplace

    Keyword Parameters:

    Attributes:

      :attr:`transconductance`

    """

    ALIAS = 'NonLinearCurrentSource'
    PREFIX = 'G'

    transconductance = ExpressionPositionalParameter(position=0, key_parameter=False)

####################################################################################################
#
# Diode
#
####################################################################################################

class Diode(FixedPinElement):

    """This class implements a junction diode.

    Spice syntax:

    .. code-block:: none

        DXXXXXXX n+ n- mname <area=val> <m=val> <pj=val> <off> <ic=vd> <temp=val> <dtemp=val>

    Keyword Parameters:

      :attr:`model`

      :attr:`area`

      :attr:`multiplier`
         alias `m`

      :attr:`pj`

      :attr:`off`

      :attr:`ic`

      :attr:`temperature`
         alias `temp`

      :attr:`device_temperature`
         alias `dtemp`

    Attributes:

      :attr:`model`

      :attr:`area`

      :attr:`multiplier`

      :attr:`pj`

      :attr:`off`

      :attr:`ic`

      :attr:`temperature`

      :attr:`device_temperature`

    """

    ALIAS = 'D'
    PREFIX = 'D'
    PINS = (('cathode', 'plus'), ('anode', 'minus'))

    model = ModelPositionalParameter(position=0, key_parameter=True)
    area = FloatKeyParameter('area')
    multiplier = IntKeyParameter('m')
    pj = FloatKeyParameter('pj')
    off = FlagParameter('off')
    ic = FloatPairKeyParameter('ic')
    temperature = FloatKeyParameter('temp', unit=U_Degree)
    device_temperature = FloatKeyParameter('dtemp', unit=U_Degree)

####################################################################################################
#
# BJTs
#
####################################################################################################

class BipolarJunctionTransistor(FixedPinElement):

    """This class implements a bipolar junction transistor.

    Spice syntax:

    .. code-block:: none

         QXXXXXXX nc nb ne <ns> mname <area=val> <areac=val> <areab=val> <m=val> <off> <ic=vbe,vce> <temp=val> <dtemp=val>

    Keyword Parameters:

      :attr:`model`

      :attr:`area`

      :attr:`areac`

      :attr:`areab`

      :attr:`multiplier`
         alias `m`

      :attr:`off`

      :attr:`ic`

      :attr:`temperature`
         alias `temp`

      :attr:`device_temperature`
         alias `dtemp`

    Attributes:

      :attr:`model`

      :attr:`area`

      :attr:`areac`

      :attr:`areab`

      :attr:`multiplier`

      :attr:`off`

      :attr:`ic`

      :attr:`temperature`

      :attr:`device_temperature`

    """

    # Fixme: off doesn't fit in kwargs !

    ALIAS = 'Q'
    LONG_ALIAS = 'BJT'
    PREFIX = 'Q'
    PINS = ('collector', 'base', 'emitter', OptionalPin('substrate'))

    model = ModelPositionalParameter(position=0, key_parameter=True)
    area = FloatKeyParameter('area')
    areac = FloatKeyParameter('areac')
    areab = FloatKeyParameter('areab')
    multiplier = IntKeyParameter('m')
    off = FlagParameter('off')
    ic = FloatPairKeyParameter('ic')
    temperature = FloatKeyParameter('temp', unit=U_Degree)
    device_temperature = FloatKeyParameter('dtemp', unit=U_Degree)

####################################################################################################
#
# JFETs
#
####################################################################################################

class JfetElement(FixedPinElement):
    PINS = ('drain', 'gate', 'source')

class JunctionFieldEffectTransistor(JfetElement):

    """This class implements a bipolar junction transistor.

    Spice syntax:

    .. code-block:: none

         JXXXXXXX nd ng ns mname <area> <off> <ic=vds,vgs> <temp=t>

    Keyword Parameters:

      :attr:`model`

      :attr:`area`

      :attr:`off`

      :attr:`ic`

      :attr:`temperature`
         alias `temp`

    Attributes:

      :attr:`model`

      :attr:`area`

      :attr:`off`

      :attr:`ic`

      :attr:`temperature`

    """

    # Fixme: off doesn't fit in kwargs !

    ALIAS = 'J'
    LONG_ALIAS = 'JFET'
    PREFIX = 'J'

    model = ModelPositionalParameter(position=0, key_parameter=True)
    area = FloatKeyParameter('area')
    multiplier = IntKeyParameter('m')
    off = FlagParameter('off')
    ic = FloatPairKeyParameter('ic')
    temperature = FloatKeyParameter('temp', unit=U_Degree)

####################################################################################################
#
# MESFETs
#
####################################################################################################

class Mesfet(JfetElement):

    """This class implements a Metal Semiconductor Field Effect Transistor.

    Spice syntax:

    .. code-block:: none

         ZXXXXXXX nd ng ns mname <area> <off> <ic=vds,vgs>

    Keyword Parameters:

      :attr:`model`

      :attr:`area`

      :attr:`off`

      :attr:`ic`

    Attributes:

      :attr:`model`

      :attr:`area`

      :attr:`off`

      :attr:`ic`

    """

    # Fixme: off doesn't fit in kwargs !

    ALIAS = 'Z'
    LONG_ALIAS = 'MESFET'
    PREFIX = 'Z'

    model = ModelPositionalParameter(position=0, key_parameter=True)
    area = FloatKeyParameter('area')
    multiplier = IntKeyParameter('m')
    off = FlagParameter('off')
    ic = FloatPairKeyParameter('ic')

####################################################################################################
#
# MOSFETs
#
####################################################################################################

class Mosfet(FixedPinElement):

    """This class implements a Metal Oxide Field Effect Transistor.

    Spice syntax:

    .. code-block:: none

         MXXXXXXX nd ng ns nb mname <m=val> <l=val> <w=val>
         + <ad=val> <as=val> <pd=val> <ps=val> <nrd=val>
         + <nrs=val> <off> <ic=vds,vgs,vbs> <temp=t>

    Keyword Parameters:

      :attr:`model`

      :attr:`multiplier`
         alias `m`

      :attr:`length`
         alias `l`

      :attr:`width`
         alias `w`

      :attr:`nfin`
        only for Xyce

      :attr:`drain_area`
          alias `ad`

      :attr:`source_area`
          alias `as`

      :attr:`drain_perimeter`
          alias `pd`

      :attr:`source_perimeter`
          alias `ps`

      :attr:`drain_number_square`
          alias `nrd`

      :attr:`source_number_square`
          alias `nrs`

      :attr:`off`

      :attr:`ic`

      :attr:`temperature`
         alias `temp`

    Attributes:

      :attr:`model`

      :attr:`multiplier`

      :attr:`length`

      :attr:`width`

      :attr:`nfin`
        only for Xyce

      :attr:`drain_area`

      :attr:`source_area`

      :attr:`drain_perimeter`

      :attr:`source_perimeter`

      :attr:`drain_number_square`

      :attr:`source_number_square`

      :attr:`off`

      :attr:`ic`

      :attr:`temperature`

    """

    # Fixme: off doesn't fit in kwargs !

    ALIAS = 'M'
    LONG_ALIAS = 'MOSFET'
    PREFIX = 'M'
    PINS = ('drain', 'gate', 'source', ('bulk', 'substrate'))

    model = ModelPositionalParameter(position=0, key_parameter=True)
    multiplier = IntKeyParameter('m')
    length = FloatKeyParameter('l', unit=U_m)
    width = FloatKeyParameter('w', unit=U_m)
    drain_area = FloatKeyParameter('ad')
    source_area = FloatKeyParameter('as')
    drain_perimeter = FloatKeyParameter('pd')
    source_perimeter = FloatKeyParameter('ps')
    drain_number_square = FloatKeyParameter('nrd')
    source_number_square = FloatKeyParameter('nrs')
    off = FlagParameter('off')
    ic = FloatTripletKeyParameter('ic')
    temperature = FloatKeyParameter('temp', unit=U_Degree)

    # only for Xyce
    nfin = IntKeyParameter('nfin')

####################################################################################################
#
# Transmission Lines
#
####################################################################################################

class LosslessTransmissionLine(TwoPortElement):

    """This class implements a lossless transmission line.

    Spice syntax:

    .. code-block:: none

        TXXXXXXX N1 N2 N3 N4 Z0=VALUE <TD=VALUE> <F=FREQ <NL=NRMLEN>> <IC=V1, I1, V2, I2>

    where TD or F, NL must be specified.

    Keyword Parameters:

      :attr:`impedance`
         alias:`Z0`
         is the characteristic impedance

      :attr:`time_delay`
         alias:`TD`
         is the transmission delay

      :attr:`frequency`
         alias:`F`

      :attr:`normalized_length`
         alias:`NL`

    Attributes:

      :attr:`impedance`

      :attr:`time_delay`

      :attr:`frequency`

      :attr:`normalized_length`

    The transmission delay, `td`, may be specified directly (as `td=10ns`, for example).
    Alternatively, a frequency `f` may be given, together with `nl`, the normalized electrical
    length of the transmission line with respect to the wavelength in the line at the frequency
    `f`. If a frequency is specified but `nl` is omitted, 0.25 is assumed (that is, the frequency is
    assumed to be the quarter-wave frequency). Note that although both forms for expressing the line
    length are indicated as optional, one of the two must be specified.

    Note: Either time_delay or frequency must be given.

    """

    ALIAS = 'TransmissionLine'
    PREFIX = 'T'

    impedance = FloatKeyParameter('Z0', default=50, unit=U_Ω)
    time_delay = FloatKeyParameter('TD', unit=U_s)
    frequency = FloatKeyParameter('F', unit=U_Hz)
    normalized_length = FloatKeyParameter('NL')

    ##############################################

    def __init__(self, name, *args, **kwargs):

        super().__init__(name, *args, **kwargs)

        if not (self.has_parameter('time_delay') or
                (self.has_parameter('frequency') and self.has_parameter('normalized_length'))):
            raise NameError('Either TD or F, NL must be specified')

####################################################################################################

class LossyTransmission(TwoPortElement):

    """This class implements lossy transmission lines.

    Spice syntax:

    .. code-block:: none

        OXXXXXXX n1 n2 n3 n4 model

    Attributes:

      :attr:`model`

    .. note:: As opposite to Spice, the model is specified before the nodes so as to act as `*args`.

    """

    ALIAS = 'O'
    PREFIX = 'O'

    model = ModelPositionalParameter(position=0, key_parameter=True)

####################################################################################################

class CoupledMulticonductorLine(NPinElement):

    """This class implements coupled multiconductor lines.

    Spice syntax:

    .. code-block:: none

        PXXXXXXX NI1 NI2 ... NIX GND1 NO1 NO2 ... NOX GND2 model <len=length>

    Attributes:

      :attr:`model`

      :attr:`length`
         alias `len`
         length of the line in meters

    .. note:: As opposite to Spice, the model is specified before the nodes so as to act as `*args`.

    """

    ALIAS = 'P'
    PREFIX = 'P'

    model = ModelPositionalParameter(position=0, key_parameter=True)
    length = FloatKeyParameter('len', unit=U_m)

    ##############################################

    def __init__(self, netlist, name, *nodes, **parameters):

        super().__init__(netlist, name, nodes, **parameters)

####################################################################################################

class UniformDistributedRCLine(FixedPinElement):

    """This class implements uniform distributed RC lines.

    Spice syntax:

    .. code-block:: none

        UXXXXXXX n1 n2 n3 model l=length <n=number_of_lumps>

    Attributes:

      :attr:`model`

      :attr:`length`
         alias `l`
         length of the RC line in meters

      :attr:`number_of_lumps`
         alias `n`

    .. note:: As opposite to Spice, the model is specified before the nodes so as to act as `*args`.

    """

    ALIAS = 'U'
    PREFIX = 'U'
    PINS = ('output', 'input', 'capacitance_node')

    model = ModelPositionalParameter(position=0, key_parameter=True)
    length = FloatKeyParameter('l', unit=U_m)
    number_of_lumps = IntKeyParameter('n')

####################################################################################################

class SingleLossyTransmissionLine(TwoPortElement):

    # Fixme: special TwoPortElement

    """This class implements single lossy transmission lines.

    Spice syntax:

    .. code-block:: none

        YXXXXXXX N1 0 N2 0 model <len=length>

    Attributes:

      :attr:`model`

      :attr:`length`
         alias `len`
         length of the line in meters

    .. note:: As opposite to Spice, the model is specified before the nodes so as to act as `*args`.

    """

    ALIAS = 'Y'
    PREFIX = 'Y'

    model = ModelPositionalParameter(position=0, key_parameter=True)
    length = FloatKeyParameter('len', unit=U_m)

####################################################################################################
#
# XSPICE
#
####################################################################################################

class XSpiceElement(NPinElement):

    """This class implements a sub-circuit.

    Spice syntax:

    .. code-block:: none

        AXXXXXXX <%v ,%i ,%vd ,%id ,%g,%gd ,%h,%hd , or %d>
        + <[> <~><%v ,%i ,%vd ,%id ,%g,%gd ,%h,%hd , or %d>
        + <NIN1 or +NIN1 -NIN1 or "null">
        + <~>...< NIN2 .. <]> >
        + <%v ,%i ,%vd ,%id ,%g,%gd ,%h,%hd ,%d or %vnam >
        + <[> <~><%v ,%i ,%vd ,%id ,%g,%gd ,%h,%hd ,
        or %d>< NOUT1 or +NOUT1 -NOUT1 >
        + <~>...< NOUT2 .. <]>>
        + MODELNAME

        . MODEL MODELNAME MODELTYPE
        + <( PARAMNAME1 = <[> VAL1 <VAL2 ... <]>> PARAMNAME2 ..>)>

    Attributes:

      :attr:`model`

    .. note:: As opposite to Spice, the model is specified before the nodes so as to act as `*args`.

    .. warning:: Partially implemented.
    """

    ALIAS = 'A'
    PREFIX = 'A'

    model = ModelPositionalParameter(position=0, key_parameter=True)

    ##############################################

    def __init__(self, netlist, name, *nodes, **parameters):

        # Fixme: ok ???

        super().__init__(netlist, name, nodes, **parameters)

####################################################################################################
#
# GSS
#
####################################################################################################

class GSSElement(NPinElement):

    """This class implements GSS device.

    .. warning:: Not implemented
    """

    ALIAS = 'N'
    PREFIX = 'N'

    ##############################################

    def __init__(self):

        raise NotImplementedError
