""""""

# Standard library modules.

# Third party modules.

# Local modules.
from pypenelopetools.penelope.keyword import \
    TypeKeyword, KeywordSequence, KeywordGroup
import pypenelopetools.penelope.keywords as penelope_keywords

# Globals and constants variables.

class SBOX(TypeKeyword):
    """
    Extended source box. The source has uniform activity within
    the volume of a right prism centred at the point (SX0,SY0,
    SZ0) and whose sides have lengths SSX, SSY and SSZ.
      DEFAULT: SSX=SSY=SSZ=0.0
    """

    def __init__(self):
        super().__init__("SPOSIT", (float, float, float),
                         comment="Source box dimensions")

    def set(self, ssx, ssy, ssz):
        super().set(ssx, ssy, ssz)

class SBODY(KeywordSequence):
    """
    In the case of a extended source, the active volume can be restricted
    to that of a body or a set of bodies, which must be defined as parts
    of the geometry. The activity of the source is assumed to be uniform
    within the volume of the intersection of the active bodies and the
    source box. Note that the initial coordinates of primary particles
    are sampled by the rejection method; the sampling efficiency is equal
    to the fraction of the source box volume that is occupied by active
    bodies.
    
    To define each active source body, add the following keyword.
    
    Active source body (PENGEOM sequential body label). 
    One line for each body.
    DEFAULT: None
    
    The program stops if the source box has not been defined previously.
    """

    def __init__(self, maxlength=5000):
        keyword = TypeKeyword("SBODY", (int,),
                              comment='Active source body; one line for each body')
        super().__init__(keyword, maxlength)

    def add(self, kb):
        """
        :arg kb: index of the body or a 
            :class:`Module <pypenelopetools.pengeom.module.Module>` object from 
            a :class:`Geometry <penelopetools.pengeom.geometry.Geometry>` object
        """
        return super().add(kb)

class IPSFN(KeywordSequence):
    """
    The initial state variables of primary particles can be read directly
    from a set of pre-calculated phase-space files (psf). When this
    option is active, previous definitions about the source are ignored.
    Photons from the psf's are assumed to be unpolarised.
    
    Name of an input psf (up to 20 characters).
        DEFAULT: none
    Up to 100 psf's may be declared. They are read sequentially.
    
    The input psf is in ASCII format. Each line defines the initial state
    of a particle; it contains the following quantities in free format
    (and in the order they are listed here):
    -- KPAR, type of particle (1, electron; 2, photon; 3, positron).
    -- E, energy.
    -- X,Y,Z, position coordinates.
    -- U,V,W, direction cosines.
    -- WGHT, weight.
    -- ILB(1),ILB(2),ILB(3),ILB(4), a set of indices that provide
           information on how the particle was generated (see the file
           'manual.txt').
    -- NSHI, incremental shower number (difference between the shower
           numbers of the present particle and the one preceding it
           in the psf).
    Phase-space files can be generated by running PENMAIN using an impact
    detector with the flag IPSF=1 (see below).
    """

    def __init__(self, maxlength=100):
        keyword = TypeKeyword("IPSFN", (str,),
                              comment="Input psf name, up to 20 characters")
        super().__init__(keyword, maxlength)

    def add(self, filename):
        return super().add(filename)

class IPSPLI(TypeKeyword):
    """
    Because of the limited size of the psf's, the results of analogue
    simulations tend to be 'too noisy'. This can be partially corrected
    by splitting the particles from the psf.
    
    Splitting number. Each particle in the psf's will be split
    into NSPLIT equivalent particles, with weights equal to
    WGHT/NSPLIT.
      DEFAULT: NSPLIT=1 (no splitting)
    """

    def __init__(self):
        super().__init__("IPSPLI", (int,),
                         comment="Splitting number")

    def set(self, nsplit):
        super().set(nsplit)

class WGTWIN(TypeKeyword):
    """
    Weight window, (WGMIN,WGMAX). Particles in the phase-space
    file that have initial weights WGHT less than WGMIN will be
    subjected to Russian roulette, and those with WGHT larger
    than WGMAX will be split. Note that the weight window has
    preference over the splitting option, i.e., a particle will
    be split into NSPLIT or less particles only if the latter
    have weights larger than WGMIN.
      DEFAULTS: WGMIN=1.0E-35, WGMAX=1.0E35  (no action)
    """

    def __init__(self):
        super().__init__("WGTWIN", (float, float),
                         comment="Weight window, RR & spl of psf particles")

    def set(self, wgmin, wgmax):
        super().set(wgmin, wgmax)

class EPMAX(TypeKeyword):
    """
    Maximum energy (in eV) of particles in the psf's.
    EPMAX is the upper limit of the energy interval covered by
    the simulation lookup tables. To minimize interpolation
    errors, EPMAX should not be much larger than the maximum
    energy actually occurring during the simulation.
    
    When the initial state variables of particles are read from
    a psf, this parameter is required to initialise PENELOPE and
    is critical; the code crashes if it finds a particle that
    has energy larger than EPMAX.
      DEFAULT: EPMAX=1.0E9 (interpolation is not optimal)
    """

    def __init__(self):
        super().__init__("EPMAX", (float,),
                         comment="Maximum energy of particles in the psf")

    def set(self, epmax):
        super().set(epmax)

class PARINP(KeywordSequence):
    """
    The values of certain parameters of the geometry definition
    may be defined from the main program by means of the array
    PARINP (an input argument of the GEOMIN subroutine). The
    entered PARINP(IP) value replaces the parameter values that
    are marked with the index IP in the geometry definition
    file.
      DEFAULT:  none
    """

    def __init__(self, maxlength=500):
        keyword = TypeKeyword("PARINP", (int, float),
                              comment="Replacement parameter")
        super().__init__(keyword, maxlength)

    def add(self, ip, parinp):
        return super().add(ip, parinp)

class ImpactDetectorGroup(KeywordGroup):
    """
    Each impact detector consists of a set of active bodies, which must
    have been defined as parts of the geometry. The output spectrum is
    the energy distribution of particles that entered any of the active
    bodies coming from a body that is not active (i.e. that is not part
    of the detector). Notice that a detected particle can re-enter the
    detector volume and, consequently, be 'counted' several times (except
    when the flag IDCUT is set equal to 0, see below).
    
    Active bodies cannot be void, because the geometry routines would not
    stop particles at their limiting surfaces. In case you need to define
    detectors outside the material system, fill them with an arbitrary
    material of very small density to avoid perturbing the transport
    process.
    
    To define each impact detector, insert the following block of lines;
    
    IMPDET : Starts the definition of a new detector. Up to 25 different
             detectors can be considered.
             EL and EU are the lower and upper limits of the energy
               window covered by the impact detector.
             NBE is the number of bins in the output energy spectrum of
               the detector (.LE. 1000). If NBE is positive, energy bins
               have uniform width, DE=(EU-EL)/NBE. When NBE is negative,
               the bin width increases geometrically with the energy,
               i.e., the energy bins have uniform width on a logarithmic
               scale.
    
             The integer flag IPSF serves to activate the creation of a
             phase-space file (psf), which contains the state variables
             of all particles that enter the detector. Use this option
             with care, because psf's may grow very fast.
             IPSF=0; no psf is created.
             IPSF=1; the psf is created. Only one PSF can be created in
               each simulation run.
    
             The integer flag IDCUT allows discontinuing the tracking of
             particles that enter the detector.
             IDCUT=0; the simulation of a particle is discontinued when
               it enters the detector (useful to stop the simulation of
               particles recorded in a psf).
             IDCUT=1; the presence of the detector does not affect the
               tracking of particles.
             IDCUT=2; the presence of the detector does not affect the
               tracking of particles. The distribution of particle
               fluence with respect to energy (integrated over the volume
               of the detector) is tallied. The calculated distribution
               has dimensions of length/energy.
    
               DEFAULTS: None
    
    IDPSF_ : Name of the output phase-space file (up to 20 characters).
               DEFAULT: 'psf-impdet-##.dat'
    
    IDSPC_ : Name of the output energy spectrum file (up to 20
             characters).
               DEFAULT: 'spc-impdet-##.dat'
    
    IDFLNC : Name of the output file with the energy distribution of
             particle fluence (20 characters). This file is generated
             only when IDCUT=2.
               DEFAULT: 'fln-impdet-##.dat'
    
    IDAGEL : Activates the evaluation of the age of particles, defined as
             the time elapsed since the start of the primary particle
             that originated the shower. The program generates the age
             distribution of detected particles, i.e., particles of the
             types declared in lines IDKPAR (see below) that enter the
             detector with energy in the window (EL,EU). The distribution
             is tallied for ages in the interval between AGEL and AGEU
             (both in seconds), which is partitioned into NAGE bins. If
             NAGE is positive, the age bins have uniform width. When
             NAGE is negative, the width of age bins is uniform on a
             logarithmic scale.
    
               DEFAULTS: NAGE=100, AGEL=0.0, AGEU must always be
                         specified
    
    IDAGEF : Name of the output age distribution file (up to 20
             characters)
               DEFAULT: 'age-impdet-##.dat'
    
    IDBODY : Active body of the detector. One line for each active body.
               DEFAULT: None
             --> Notice that a body cannot be part of more than one
             impact detector.
    
    IDKPAR : Type of particle that is detected (1=electrons, 2=photons or
             3=positrons). One line for each type.
    
             The detector has no effect for particles that are not
             detected. This feature can be used, e.g., to make a body or
             a set of bodies opaque to particles of a certain type.
    
               DEFAULT: All particles are detected
   """

    def __init__(self):
        self.IMPDET = TypeKeyword('IMPDET', (float, float, int, int, int),
                                  comment='E-window, no. of bins, IPSF, IDCUT')
        self.IDSPC = TypeKeyword('IDSPC', (str,),
                                 comment='Spectrum file name, 20 chars')
        self.IDPSF = TypeKeyword('IDPSF', (str,),
                                 comment='Phase-space file name, 20 chars')
        self.IDFLNC = TypeKeyword('IDFLNC', (str,),
                                  comment='Fluence spectrum file name, 20 chars')
        self.IDAGEL = TypeKeyword('IDAGEL', (float, float, int),
                                  comment='Age interval and no. of bins')
        self.IDAGEF = TypeKeyword('IDAGEF', (str,),
                                  comment='Age-distribution file name, 20 chars')

        keyword = TypeKeyword('IDBODY', (int,), comment='Active body')
        self.IDBODY = KeywordSequence(keyword, maxlength=5000)

        keyword = TypeKeyword('IDKPAR', (int,), comment='Kind of detected particles')
        self.IDKPAR = KeywordSequence(keyword, maxlength=3)

    def get_keywords(self):
        return (self.IMPDET, self.IDSPC, self.IDPSF, self.IDFLNC,
                self.IDAGEL, self.IDAGEF, self.IDBODY, self.IDKPAR)

    def set(self, el, eu, nbe, ipsf, idcut,
            spectrum_filename=None, psf_filename=None, fln_filename=None,
            agel=None, ageu=None, nage=None, age_filename=None,
            kb=None, kpar=None):
        self.IMPDET.set(el, eu, nbe, ipsf, idcut)
        self.IDSPC.set(spectrum_filename)
        self.IDPSF.set(psf_filename)
        self.IDFLNC.set(fln_filename)
        self.IDAGEL.set(agel, ageu, nage)
        self.IDAGEF.set(age_filename)
        self._set_keyword_sequence(self.IDBODY, kb)
        self._set_keyword_sequence(self.IDKPAR, kpar)

class ImpactDetectors(KeywordSequence):

    def __init__(self, maxlength=25):
        keyword = ImpactDetectorGroup()
        super().__init__(keyword, maxlength)

    def add(self, el, eu, nbe, ipsf, idcut,
            spectrum_filename=None, psf_filename=None, fln_filename=None,
            agel=None, ageu=None, nage=None, age_filename=None,
            kb=None, kpar=None):
        return super().add(el, eu, nbe, ipsf, idcut,
                           spectrum_filename, psf_filename, fln_filename,
                           agel, ageu, nage, age_filename,
                           kb, kpar)

class EnergyDepositionDetectorGroup(KeywordGroup):
    """
    Each energy-deposition detector consists of a set of active bodies,
    which must have been defined as parts of the geometry. The output
    spectrum is the distribution of absorbed energy (per primary shower)
    in the active bodies.
    
    .. warning::
       The energy-deposition spectrum may be strongly biased when interaction 
       forcing is applied, even outside the detector bodies.
    """

    def __init__(self):
        self.ENDETC = penelope_keywords.ENDETC()
        self.EDSPC = penelope_keywords.EDSPC()

        keyword = TypeKeyword('EDBODY', (int,), comment='Active body')
        self.EDBODY = KeywordSequence(keyword, maxlength=5000)

    def get_keywords(self):
        return (self.ENDETC, self.EDSPC, self.EDBODY)

    def set(self, el, eu, nbe, spectrum_filename=None, kb=None):
        self.ENDETC.set(el, eu, nbe)
        self.EDSPC.set(spectrum_filename)
        self._set_keyword_sequence(self.EDBODY, kb)

class EnergyDepositionDetectors(KeywordSequence):

    def __init__(self, maxlength=25):
        keyword = EnergyDepositionDetectorGroup()
        super().__init__(keyword, maxlength)

    def add(self, el, eu, nbe, spectrum_filename=None, kb=None):
        return super().add(el, eu, nbe, spectrum_filename, kb)

class GRIDR(TypeKeyword):
    """
    The efficiency of the dose map calculation can be increased by taking
    advantage of possible symmetries of the system (source and geometry).
    In problems with axial symmetry about the Z axis, it is advantageous
    to tally the dose distribution in the volume of a cylinder of radius
    RU, about the Z axis, limited by the planes Z=ZL and Z=ZU. For
    systems with spherical symmetry about the origin of coordinates, it
    is most convenient to consider the radial dose distribution in a
    sphere of radius RU. The generation of these symmetric dose maps is
    activated by entering the line
    
    GRIDR_ : Radius RU of the dose zone and number of radial bins.
               DEFAULT: None
    
    Specifically, when the input file contains only the lines GRIDZ_ and
    GRIDR_, the program assumes that the dose distribution is axially
    symmetric and generates a cylindrical map. When the input file has
    only the line GRIDR_, spherical symmetry is assumed and the radial
    distribution of absorbed dose is tallied.
    
    The different types of dose maps are mutually exclusive. Notice that
    when the assumed symmetry does not hold, the program may not be able
    to evaluate the masses of voxels correctly.
    """

    def __init__(self):
        super().__init__('GRIDR', (float, int),
                         comment='Radius of the dose volume, no. of bins')

    def set(self, ru, ndbr):
        super().set(ru, ndbr)




