r"""
The module :mod:`~xrt.backends.raycing.materials_compounds` contains predefined
classes for compound materials found at the
`CXRO Table of Densities of Common Materials
<https://henke.lbl.gov/cgi-bin/density.pl>`_.
Air composition and density are given as in [Cox]_.

.. [Cox] Cox, Arthur N., ed. (2000), Allen's Astrophysical Quantities
   (Fourth ed.), AIP Press, pp. 258–259, ISBN 0-387-98746-0

To use a compound material in a script, simply import the module and
instantiate its class:

.. code-block:: python

    import xrt.backends.raycing.materials_compounds as xcomp
    kapton = xcomp.Polyimide()

The compound materials inherit from :class:`.Material` and can use its methods
to calculate reflection or transmission amplitudes, absorption coefficient,
refractive index etc.

.. note::
    The compound materials do not provide crystal diffraction amplitudes even
    if they occur naturally as crystals. To calculate diffraction on crystals
    please use :mod:`~xrt.backends.raycing.materials_crystals`.

The following compound classes are defined in this module (sorted by chemical
formula):

   | |compall|

"""
__author__ = "Roman Chernikov, Konstantin Klementiev"
__date__ = "6 Jul 2017"

from . import materials as rmat
import collections

__all__ = ('SilverBromide', 'AluminumArsenide', 'Sapphire',
           'AluminumPhosphide', 'BoronOxide', 'BoronCarbide', 'BerylliumOxide',
           'BoronNitride', 'Polyimide', 'Polypropylene', 'PMMA',
           'Polycarbonate', 'Kimfol', 'Mylar', 'Teflon', 'ParyleneC',
           'ParyleneN', 'Fluorite', 'CadmiumTungstate', 'CadmiumSulfide',
           'CadmiumTelluride', 'CobaltSilicide', 'Cromium3Oxide',
           'CesiumIodide', 'CopperIodide', 'IndiumNitride', 'Indium3Oxide',
           'IndiumAntimonide', 'IridiumOxide', 'GalliumArsenide',
           'GalliumNitride', 'GalliumPhosphide', 'HafniumOxide',
           'LithiumFluoride', 'LithiumHydride', 'LithiumHydroxide',
           'MagnesiumFluoride', 'MagnesiumOxide', 'MagnesiumSilicide',
           'Mica', 'Manganese2Oxide', 'Manganese4Oxide', 'Molybdenum4Oxide',
           'Molybdenum6Oxide', 'MolybdenumSilicide', 'RockSalt',
           'NiobiumSilicide', 'NiobiumNitride', 'NickelOxide',
           'NickelSilicide', 'RutheniumSilicide', 'Ruthenium4Oxide',
           'SiliconCarbide', 'SiliconNitride', 'Silica', 'Quartz',
           'TantalumNitride', 'TantalumOxide', 'TitaniumNitride',
           'TitaniumSilicide', 'TantalumSilicide', 'Rutile', 'ULEGlass',
           'Uranium4Oxide', 'VanadiumNitride', 'Vacuum', 'Water',
           'TungstenCarbide', 'Zerodur', 'ZinсOxide', 'ZinсSulfide',
           'ZirconiumNitride', 'Zirconia', 'ZirconiumSilicide', 'Air',
           'CVDDiamond',
           )

__allSectioned__ = collections.OrderedDict([
    ('Li-C',
        ('LithiumFluoride', 'LithiumHydride', 'LithiumHydroxide',
         'BoronOxide', 'BoronCarbide', 'BoronNitride', 'Polyimide',
         'Polypropylene', 'PMMA', 'Polycarbonate', 'Kimfol', 'Mylar',
         'Teflon', 'ParyleneC', 'ParyleneN', 'CVDDiamond')),
    ('Na-Ca',
        ('RockSalt', 'MagnesiumFluoride', 'MagnesiumOxide',
         'MagnesiumSilicide', 'Sapphire', 'AluminumPhosphide',
         'AluminumArsenide', 'SiliconCarbide', 'SiliconNitride',
         'Silica', 'Quartz', 'ULEGlass', 'Zerodur', 'Mica', 'Fluorite')),
    ('Ti-Ga',
        ('TitaniumNitride', 'TitaniumSilicide', 'Rutile', 'VanadiumNitride',
         'Cromium3Oxide', 'Manganese2Oxide', 'Manganese4Oxide',
         'CobaltSilicide', 'NickelOxide', 'NickelSilicide', 'CopperIodide',
         'ZinсOxide', 'ZinсSulfide', 'GalliumNitride', 'GalliumPhosphide',
         'GalliumArsenide')),
    ('Zr-Cd',
        ('ZirconiumNitride', 'Zirconia', 'ZirconiumSilicide', 'NiobiumNitride',
         'NiobiumSilicide', 'Molybdenum4Oxide', 'Molybdenum6Oxide',
         'MolybdenumSilicide', 'Ruthenium4Oxide', 'RutheniumSilicide',
         'SilverBromide', 'CadmiumSulfide', 'CadmiumTelluride',
         'CadmiumTungstate')),
    ('In-U',
        ('Indium3Oxide', 'IndiumNitride', 'IndiumAntimonide', 'CesiumIodide',
         'HafniumOxide', 'TantalumOxide', 'TitaniumNitride',
         'TitaniumSilicide', 'TungstenCarbide',
         'IridiumOxide', 'Uranium4Oxide')),
    ('Other',
        ('Vacuum', 'Air', 'Water'))])

class SilverBromide(rmat.Material):
    def __init__(self, name='AgBr',
                 elements=['Ag', 'Br'],
                 quantities=[1, 1],
                 rho=6.473, *args, **kwargs):
        super().__init__(
           name=name, rho=rho, elements=elements, quantities=quantities,
           *args, **kwargs)

class AluminumArsenide(rmat.Material):
    def __init__(self, name='AlAs',
                 elements=['Al', 'As'],
                 quantities=[1, 1],
                 rho=3.81, *args, **kwargs):
        super().__init__(
           name=name, rho=rho, elements=elements, quantities=quantities,
           *args, **kwargs)

class Sapphire(rmat.Material):
    def __init__(self, name='Al2O3',
                 elements=['Al', 'O'],
                 quantities=[2.0, 3.0],
                 rho=3.97, *args, **kwargs):
        super().__init__(
           name=name, rho=rho, elements=elements, quantities=quantities,
           *args, **kwargs)

class AluminumPhosphide(rmat.Material):
    def __init__(self, name='AlP',
                 elements=['Al', 'P'],
                 quantities=[1, 1],
                 rho=2.42, *args, **kwargs):
        super().__init__(
           name=name, rho=rho, elements=elements, quantities=quantities,
           *args, **kwargs)

class BoronOxide(rmat.Material):
    def __init__(self, name='B2O3',
                 elements=['B', 'O'],
                 quantities=[2.0, 3.0],
                 rho=3.11, *args, **kwargs):
        super().__init__(
           name=name, rho=rho, elements=elements, quantities=quantities,
           *args, **kwargs)

class BoronCarbide(rmat.Material):
    def __init__(self, name='B4C',
                 elements=['B', 'C'],
                 quantities=[4.0, 1],
                 rho=2.52, *args, **kwargs):
        super().__init__(
           name=name, rho=rho, elements=elements, quantities=quantities,
           *args, **kwargs)

class BerylliumOxide(rmat.Material):
    def __init__(self, name='BeO',
                 elements=['Be', 'O'],
                 quantities=[1, 1],
                 rho=3.01, *args, **kwargs):
        super().__init__(
           name=name, rho=rho, elements=elements, quantities=quantities,
           *args, **kwargs)

class BoronNitride(rmat.Material):
    def __init__(self, name='BN',
                 elements=['B', 'N'],
                 quantities=[1, 1],
                 rho=2.25, *args, **kwargs):
        super().__init__(
           name=name, rho=rho, elements=elements, quantities=quantities,
           *args, **kwargs)

class Polyimide(rmat.Material):
    def __init__(self, name='C22H10N2O5',
                 elements=['C', 'H', 'N', 'O'],
                 quantities=[22.0, 10.0, 2.0, 5.0],
                 rho=1.43, *args, **kwargs):
        super().__init__(
           name=name, rho=rho, elements=elements, quantities=quantities,
           *args, **kwargs)

class Polypropylene(rmat.Material):
    def __init__(self, name='C3H6',
                 elements=['C', 'H'],
                 quantities=[3.0, 6.0],
                 rho=0.9, *args, **kwargs):
        super().__init__(
           name=name, rho=rho, elements=elements, quantities=quantities,
           *args, **kwargs)

class PMMA(rmat.Material):
    def __init__(self, name='C5H8O2',
                 elements=['C', 'H', 'O'],
                 quantities=[5.0, 8.0, 2.0],
                 rho=1.19, *args, **kwargs):
        super().__init__(
           name=name, rho=rho, elements=elements, quantities=quantities,
           *args, **kwargs)

class Polycarbonate(rmat.Material):
    def __init__(self, name='C16H14O3',
                 elements=['C', 'H', 'O'],
                 quantities=[16.0, 14.0, 3.0],
                 rho=1.2, *args, **kwargs):
        super().__init__(
           name=name, rho=rho, elements=elements, quantities=quantities,
           *args, **kwargs)

class Kimfol(rmat.Material):
    def __init__(self, name='C16H14O3',
                 elements=['C', 'H', 'O'],
                 quantities=[16.0, 14.0, 3.0],
                 rho=1.2, *args, **kwargs):
        super().__init__(
           name=name, rho=rho, elements=elements, quantities=quantities,
           *args, **kwargs)

class Mylar(rmat.Material):
    def __init__(self, name='C10H8O4',
                 elements=['C', 'H', 'O'],
                 quantities=[10.0, 8.0, 4.0],
                 rho=1.4, *args, **kwargs):
        super().__init__(
           name=name, rho=rho, elements=elements, quantities=quantities,
           *args, **kwargs)

class Teflon(rmat.Material):
    def __init__(self, name='C2F4',
                 elements=['C', 'F'],
                 quantities=[2.0, 4.0],
                 rho=2.2, *args, **kwargs):
        super().__init__(
           name=name, rho=rho, elements=elements, quantities=quantities,
           *args, **kwargs)

class ParyleneC(rmat.Material):
    def __init__(self, name='C8H7Cl',
                 elements=['C', 'H', 'Cl'],
                 quantities=[8.0, 7.0, 1],
                 rho=1.29, *args, **kwargs):
        super().__init__(
           name=name, rho=rho, elements=elements, quantities=quantities,
           *args, **kwargs)

class ParyleneN(rmat.Material):
    def __init__(self, name='C8H8',
                 elements=['C', 'H'],
                 quantities=[8.0, 8.0],
                 rho=1.11, *args, **kwargs):
        super().__init__(
           name=name, rho=rho, elements=elements, quantities=quantities,
           *args, **kwargs)

class Fluorite(rmat.Material):
    def __init__(self, name='CaF2',
                 elements=['Ca', 'F'],
                 quantities=[1, 2.0],
                 rho=3.18, *args, **kwargs):
        super().__init__(
           name=name, rho=rho, elements=elements, quantities=quantities,
           *args, **kwargs)

class CadmiumTungstate(rmat.Material):
    def __init__(self, name='CdWO4',
                 elements=['Cd', 'W', 'O'],
                 quantities=[1, 1, 4.0],
                 rho=7.9, *args, **kwargs):
        super().__init__(
           name=name, rho=rho, elements=elements, quantities=quantities,
           *args, **kwargs)

class CadmiumSulfide(rmat.Material):
    def __init__(self, name='CdS',
                 elements=['Cd', 'S'],
                 quantities=[1, 1],
                 rho=4.826, *args, **kwargs):
        super().__init__(
           name=name, rho=rho, elements=elements, quantities=quantities,
           *args, **kwargs)

class CadmiumTelluride(rmat.Material):
    def __init__(self, name='CdTe',
                 elements=['Cd', 'Te'],
                 quantities=[1, 1],
                 rho=5.85, *args, **kwargs):
        super().__init__(
           name=name, rho=rho, elements=elements, quantities=quantities,
           *args, **kwargs)

class CobaltSilicide(rmat.Material):
    def __init__(self, name='CoSi2',
                 elements=['Co', 'Si'],
                 quantities=[1, 2.0],
                 rho=5.3, *args, **kwargs):
        super().__init__(
           name=name, rho=rho, elements=elements, quantities=quantities,
           *args, **kwargs)

class Cromium3Oxide(rmat.Material):
    def __init__(self, name='Cr2O3',
                 elements=['Cr', 'O'],
                 quantities=[2.0, 3.0],
                 rho=5.21, *args, **kwargs):
        super().__init__(
           name=name, rho=rho, elements=elements, quantities=quantities,
           *args, **kwargs)

class CesiumIodide(rmat.Material):
    def __init__(self, name='CsI',
                 elements=['Cs', 'I'],
                 quantities=[1, 1],
                 rho=4.51, *args, **kwargs):
        super().__init__(
           name=name, rho=rho, elements=elements, quantities=quantities,
           *args, **kwargs)

class CopperIodide(rmat.Material):
    def __init__(self, name='CuI',
                 elements=['Cu', 'I'],
                 quantities=[1, 1],
                 rho=5.63, *args, **kwargs):
        super().__init__(
           name=name, rho=rho, elements=elements, quantities=quantities,
           *args, **kwargs)

class IndiumNitride(rmat.Material):
    def __init__(self, name='InN',
                 elements=['In', 'N'],
                 quantities=[1, 1],
                 rho=6.88, *args, **kwargs):
        super().__init__(
           name=name, rho=rho, elements=elements, quantities=quantities,
           *args, **kwargs)

class Indium3Oxide(rmat.Material):
    def __init__(self, name='In2O3',
                 elements=['In', 'O'],
                 quantities=[2.0, 3.0],
                 rho=7.179, *args, **kwargs):
        super().__init__(
           name=name, rho=rho, elements=elements, quantities=quantities,
           *args, **kwargs)

class IndiumAntimonide(rmat.Material):
    def __init__(self, name='InSb',
                 elements=['In', 'Sb'],
                 quantities=[1, 1],
                 rho=5.775, *args, **kwargs):
        super().__init__(
           name=name, rho=rho, elements=elements, quantities=quantities,
           *args, **kwargs)

class IridiumOxide(rmat.Material):
    def __init__(self, name='IrO2',
                 elements=['Ir', 'O'],
                 quantities=[1, 2.0],
                 rho=11.66, *args, **kwargs):
        super().__init__(
           name=name, rho=rho, elements=elements, quantities=quantities,
           *args, **kwargs)

class GalliumArsenide(rmat.Material):
    def __init__(self, name='GaAs',
                 elements=['Ga', 'As'],
                 quantities=[1, 1],
                 rho=5.316, *args, **kwargs):
        super().__init__(
           name=name, rho=rho, elements=elements, quantities=quantities,
           *args, **kwargs)

class GalliumNitride(rmat.Material):
    def __init__(self, name='GaN',
                 elements=['Ga', 'N'],
                 quantities=[1, 1],
                 rho=6.1, *args, **kwargs):
        super().__init__(
           name=name, rho=rho, elements=elements, quantities=quantities,
           *args, **kwargs)

class GalliumPhosphide(rmat.Material):
    def __init__(self, name='GaP',
                 elements=['Ga', 'P'],
                 quantities=[1, 1],
                 rho=4.13, *args, **kwargs):
        super().__init__(
           name=name, rho=rho, elements=elements, quantities=quantities,
           *args, **kwargs)

class HafniumOxide(rmat.Material):
    def __init__(self, name='HfO2',
                 elements=['Hf', 'O'],
                 quantities=[1, 2.0],
                 rho=9.68, *args, **kwargs):
        super().__init__(
           name=name, rho=rho, elements=elements, quantities=quantities,
           *args, **kwargs)

class LithiumFluoride(rmat.Material):
    def __init__(self, name='LiF',
                 elements=['Li', 'F'],
                 quantities=[1, 1],
                 rho=2.635, *args, **kwargs):
        super().__init__(
           name=name, rho=rho, elements=elements, quantities=quantities,
           *args, **kwargs)

class LithiumHydride(rmat.Material):
    def __init__(self, name='LiH',
                 elements=['Li', 'H'],
                 quantities=[1, 1],
                 rho=0.783, *args, **kwargs):
        super().__init__(
           name=name, rho=rho, elements=elements, quantities=quantities,
           *args, **kwargs)

class LithiumHydroxide(rmat.Material):
    def __init__(self, name='LiOH',
                 elements=['Li', 'O', 'H'],
                 quantities=[1, 1, 1],
                 rho=1.43, *args, **kwargs):
        super().__init__(
           name=name, rho=rho, elements=elements, quantities=quantities,
           *args, **kwargs)

class MagnesiumFluoride(rmat.Material):
    def __init__(self, name='MgF2',
                 elements=['Mg', 'F'],
                 quantities=[1, 2.0],
                 rho=3.18, *args, **kwargs):
        super().__init__(
           name=name, rho=rho, elements=elements, quantities=quantities,
           *args, **kwargs)

class MagnesiumOxide(rmat.Material):
    def __init__(self, name='MgO',
                 elements=['Mg', 'O'],
                 quantities=[1, 1],
                 rho=3.58, *args, **kwargs):
        super().__init__(
           name=name, rho=rho, elements=elements, quantities=quantities,
           *args, **kwargs)

class MagnesiumSilicide(rmat.Material):
    def __init__(self, name='Mg2Si',
                 elements=['Mg', 'Si'],
                 quantities=[2.0, 1],
                 rho=1.94, *args, **kwargs):
        super().__init__(
           name=name, rho=rho, elements=elements, quantities=quantities,
           *args, **kwargs)

class Mica(rmat.Material):
    def __init__(self, name='KAl3Si3O12H2',
                 elements=['K', 'Al', 'Si', 'O', 'H'],
                 quantities=[1, 3.0, 3.0, 12.0, 2.0],
                 rho=2.83, *args, **kwargs):
        super().__init__(
           name=name, rho=rho, elements=elements, quantities=quantities,
           *args, **kwargs)

class Manganese2Oxide(rmat.Material):
    def __init__(self, name='MnO',
                 elements=['Mn', 'O'],
                 quantities=[1, 1],
                 rho=5.44, *args, **kwargs):
        super().__init__(
           name=name, rho=rho, elements=elements, quantities=quantities,
           *args, **kwargs)

class Manganese4Oxide(rmat.Material):
    def __init__(self, name='MnO2',
                 elements=['Mn', 'O'],
                 quantities=[1, 2.0],
                 rho=5.03, *args, **kwargs):
        super().__init__(
           name=name, rho=rho, elements=elements, quantities=quantities,
           *args, **kwargs)

class Molybdenum4Oxide(rmat.Material):
    def __init__(self, name='MoO2',
                 elements=['Mo', 'O'],
                 quantities=[1, 2.0],
                 rho=6.47, *args, **kwargs):
        super().__init__(
           name=name, rho=rho, elements=elements, quantities=quantities,
           *args, **kwargs)

class Molybdenum6Oxide(rmat.Material):
    def __init__(self, name='MoO3',
                 elements=['Mo', 'O'],
                 quantities=[1, 3.0],
                 rho=4.69, *args, **kwargs):
        super().__init__(
           name=name, rho=rho, elements=elements, quantities=quantities,
           *args, **kwargs)

class MolybdenumSilicide(rmat.Material):
    def __init__(self, name='MoSi2',
                 elements=['Mo', 'Si'],
                 quantities=[1, 2.0],
                 rho=6.31, *args, **kwargs):
        super().__init__(
           name=name, rho=rho, elements=elements, quantities=quantities,
           *args, **kwargs)

class RockSalt(rmat.Material):
    def __init__(self, name='NaCl',
                 elements=['Na', 'Cl'],
                 quantities=[1, 1],
                 rho=2.165, *args, **kwargs):
        super().__init__(
           name=name, rho=rho, elements=elements, quantities=quantities,
           *args, **kwargs)

class NiobiumSilicide(rmat.Material):
    def __init__(self, name='NbSi2',
                 elements=['Nb', 'Si'],
                 quantities=[1, 2.0],
                 rho=5.37, *args, **kwargs):
        super().__init__(
           name=name, rho=rho, elements=elements, quantities=quantities,
           *args, **kwargs)

class NiobiumNitride(rmat.Material):
    def __init__(self, name='NbN',
                 elements=['Nb', 'N'],
                 quantities=[1, 1],
                 rho=8.47, *args, **kwargs):
        super().__init__(
           name=name, rho=rho, elements=elements, quantities=quantities,
           *args, **kwargs)

class NickelOxide(rmat.Material):
    def __init__(self, name='NiO',
                 elements=['Ni', 'O'],
                 quantities=[1, 1],
                 rho=6.67, *args, **kwargs):
        super().__init__(
           name=name, rho=rho, elements=elements, quantities=quantities,
           *args, **kwargs)

class NickelSilicide(rmat.Material):
    def __init__(self, name='Ni2Si',
                 elements=['Ni', 'Si'],
                 quantities=[2.0, 1],
                 rho=7.2, *args, **kwargs):
        super().__init__(
           name=name, rho=rho, elements=elements, quantities=quantities,
           *args, **kwargs)

class RutheniumSilicide(rmat.Material):
    def __init__(self, name='Ru2Si3',
                 elements=['Ru', 'Si'],
                 quantities=[2.0, 3.0],
                 rho=6.96, *args, **kwargs):
        super().__init__(
           name=name, rho=rho, elements=elements, quantities=quantities,
           *args, **kwargs)

class Ruthenium4Oxide(rmat.Material):
    def __init__(self, name='RuO2',
                 elements=['Ru', 'O'],
                 quantities=[1, 2.0],
                 rho=6.97, *args, **kwargs):
        super().__init__(
           name=name, rho=rho, elements=elements, quantities=quantities,
           *args, **kwargs)

class SiliconCarbide(rmat.Material):
    def __init__(self, name='SiC',
                 elements=['Si', 'C'],
                 quantities=[1, 1],
                 rho=3.217, *args, **kwargs):
        super().__init__(
           name=name, rho=rho, elements=elements, quantities=quantities,
           *args, **kwargs)

class SiliconNitride(rmat.Material):
    def __init__(self, name='Si3N4',
                 elements=['Si', 'N'],
                 quantities=[3.0, 4.0],
                 rho=3.44, *args, **kwargs):
        super().__init__(
           name=name, rho=rho, elements=elements, quantities=quantities,
           *args, **kwargs)

class Silica(rmat.Material):
    def __init__(self, name='SiO2',
                 elements=['Si', 'O'],
                 quantities=[1, 2.0],
                 rho=2.2, *args, **kwargs):
        super().__init__(
           name=name, rho=rho, elements=elements, quantities=quantities,
           *args, **kwargs)

class Quartz(rmat.Material):
    def __init__(self, name='SiO2',
                 elements=['Si', 'O'],
                 quantities=[1, 2.0],
                 rho=2.65, *args, **kwargs):
        super().__init__(
           name=name, rho=rho, elements=elements, quantities=quantities,
           *args, **kwargs)

class TantalumNitride(rmat.Material):
    def __init__(self, name='TaN',
                 elements=['Ta', 'N'],
                 quantities=[1, 1],
                 rho=16.3, *args, **kwargs):
        super().__init__(
           name=name, rho=rho, elements=elements, quantities=quantities,
           *args, **kwargs)

class TantalumOxide(rmat.Material):
    def __init__(self, name='Ta2O5',
                 elements=['Ta', 'O'],
                 quantities=[2.0, 5.0],
                 rho=8.2, *args, **kwargs):
        super().__init__(
           name=name, rho=rho, elements=elements, quantities=quantities,
           *args, **kwargs)

class TitaniumNitride(rmat.Material):
    def __init__(self, name='TiN',
                 elements=['Ti', 'N'],
                 quantities=[1, 1],
                 rho=5.22, *args, **kwargs):
        super().__init__(
           name=name, rho=rho, elements=elements, quantities=quantities,
           *args, **kwargs)

class TitaniumSilicide(rmat.Material):
    def __init__(self, name='TiSi2',
                 elements=['Ti', 'Si'],
                 quantities=[1, 2.0],
                 rho=4.02, *args, **kwargs):
        super().__init__(
           name=name, rho=rho, elements=elements, quantities=quantities,
           *args, **kwargs)

class TantalumSilicide(rmat.Material):
    def __init__(self, name='Ta2Si',
                 elements=['Ta', 'Si'],
                 quantities=[2.0, 1],
                 rho=14, *args, **kwargs):
        super().__init__(
           name=name, rho=rho, elements=elements, quantities=quantities,
           *args, **kwargs)

class Rutile(rmat.Material):
    def __init__(self, name='TiO2',
                 elements=['Ti', 'O'],
                 quantities=[1, 2.0],
                 rho=4.26, *args, **kwargs):
        super().__init__(
           name=name, rho=rho, elements=elements, quantities=quantities,
           *args, **kwargs)

class ULEGlass(rmat.Material):
    def __init__(self, name='Si.925Ti.075O2',
                 elements=['Si', 'Ti', 'O'],
                 quantities=[0.925, 0.075, 2.0],
                 rho=2.205, *args, **kwargs):
        super().__init__(
           name=name, rho=rho, elements=elements, quantities=quantities,
           *args, **kwargs)

class Uranium4Oxide(rmat.Material):
    def __init__(self, name='UO2',
                 elements=['U', 'O'],
                 quantities=[1, 2.0],
                 rho=10.96, *args, **kwargs):
        super().__init__(
           name=name, rho=rho, elements=elements, quantities=quantities,
           *args, **kwargs)

class VanadiumNitride(rmat.Material):
    def __init__(self, name='VN',
                 elements=['V', 'N'],
                 quantities=[1, 1],
                 rho=6.13, *args, **kwargs):
        super().__init__(
           name=name, rho=rho, elements=elements, quantities=quantities,
           *args, **kwargs)

class Vacuum(rmat.Material):
    def __init__(self, name=' ',
                 elements=['H'],
                 quantities=[1],
                 rho=0, *args, **kwargs):
        super().__init__(
           name=name, rho=rho, elements=elements, quantities=quantities,
           *args, **kwargs)

class Water(rmat.Material):
    def __init__(self, name='H2O',
                 elements=['H', 'O'],
                 quantities=[2.0, 1],
                 rho=1, *args, **kwargs):
        super().__init__(
           name=name, rho=rho, elements=elements, quantities=quantities,
           *args, **kwargs)

class TungstenCarbide(rmat.Material):
    def __init__(self, name='WC',
                 elements=['W', 'C'],
                 quantities=[1, 1],
                 rho=15.63, *args, **kwargs):
        super().__init__(
           name=name, rho=rho, elements=elements, quantities=quantities,
           *args, **kwargs)

class Zerodur(rmat.Material):
    def __init__(self, name='Si.56Al.5P.16Li.04Ti.02Zr.02Zn.03O2.46',
                 elements=['Si', 'Al', 'P', 'Li', 'Ti', 'Zr', 'Zn', 'O'],
                 quantities=[0.56, 0.5, 0.16, 0.04, 0.02, 0.02, 0.03, 2.46],
                 rho=2.53, *args, **kwargs):
        super().__init__(
           name=name, rho=rho, elements=elements, quantities=quantities,
           *args, **kwargs)

class ZinсOxide(rmat.Material):
    def __init__(self, name='ZnO',
                 elements=['Zn', 'O'],
                 quantities=[1, 1],
                 rho=5.675, *args, **kwargs):
        super().__init__(
           name=name, rho=rho, elements=elements, quantities=quantities,
           *args, **kwargs)

class ZinсSulfide(rmat.Material):
    def __init__(self, name='ZnS',
                 elements=['Zn', 'S'],
                 quantities=[1, 1],
                 rho=4.079, *args, **kwargs):
        super().__init__(
           name=name, rho=rho, elements=elements, quantities=quantities,
           *args, **kwargs)

class ZirconiumNitride(rmat.Material):
    def __init__(self, name='ZrN',
                 elements=['Zr', 'N'],
                 quantities=[1, 1],
                 rho=7.09, *args, **kwargs):
        super().__init__(
           name=name, rho=rho, elements=elements, quantities=quantities,
           *args, **kwargs)

class Zirconia(rmat.Material):
    def __init__(self, name='ZrO2',
                 elements=['Zr', 'O'],
                 quantities=[1, 2.0],
                 rho=5.68, *args, **kwargs):
        super().__init__(
           name=name, rho=rho, elements=elements, quantities=quantities,
           *args, **kwargs)

class ZirconiumSilicide(rmat.Material):
    def __init__(self, name='ZrSi2',
                 elements=['Zr', 'Si'],
                 quantities=[1, 2.0],
                 rho=4.88, *args, **kwargs):
        super().__init__(
           name=name, rho=rho, elements=elements, quantities=quantities,
           *args, **kwargs)

class Air(rmat.Material):
    def __init__(self, name='N0.781O0.209Ar0.009',
                 elements=['N', 'O', 'Ar'],
                 quantities=[0.781, 0.209, 0.009],
                 rho=1.20E-06, *args, **kwargs):
        super().__init__(
           name=name, rho=rho, elements=elements, quantities=quantities,
           *args, **kwargs)

class CVDDiamond(rmat.Material):
    def __init__(self, name='C',
                 elements=['C'],
                 quantities=[1],
                 rho=3.52, *args, **kwargs):
        super().__init__(
           name=name, rho=rho, elements=elements, quantities=quantities,
           *args, **kwargs)


import os
from random import sample
from functools import cached_property

import scanpy as sc
import numpy as np
from anndata import AnnData
from tqdm import tqdm
from scipy.stats import pearsonr, zscore
from sklearn.neighbors import NearestNeighbors, KNeighborsClassifier
from sklearn.cluster import KMeans
import pandas as pd

from . import stats

def create_scanpy_object(analysis, name=None, positions=None, codebook=None, keep_empty_cells=True) -> sc.AnnData:
    cellgene = analysis.load_cell_by_gene_table()
    celldata = analysis.load_cell_metadata()
    celldata.index = celldata.index.astype(str)
    if keep_empty_cells:
        empty_cells = pd.DataFrame(
            [
                pd.Series(data=0, index=cellgene.columns, name=cellid)
                for cellid in celldata.index.difference(cellgene.index)
            ]
        )
        cellgene = pd.concat([cellgene, empty_cells])
    blank_cols = np.array(["notarget" in col or "blank" in col.lower() for col in cellgene])
    adata = sc.AnnData(cellgene.loc[:, ~blank_cols], dtype=np.int32)
    adata.obsm["X_blanks"] = cellgene.loc[:, blank_cols].to_numpy()
    adata.uns["blank_names"] = cellgene.columns[blank_cols].to_list()
    if "global_x" in celldata:
        adata.obsm["X_spatial"] = np.array(
            celldata[["global_x", "global_y"]].reindex(index=adata.obs.index)
        )
    elif "center_x" in celldata:
        adata.obsm["X_spatial"] = np.array(
            celldata[["center_x", "center_y"]].reindex(index=adata.obs.index)
        )
    if "fov_x" in celldata:
        adata.obsm["X_local"] = np.array(celldata[["fov_x", "fov_y"]].reindex(index=adata.obs.index))
    for column in celldata.columns:
        adata.obs[column] = celldata[column]
    adata.obs["fov"] = adata.obs["fov"].astype(str)
    adata.layers["counts"] = adata.X
    sc.pp.calculate_qc_metrics(adata, percent_top=None, inplace=True)
    adata.obs["blank_counts"] = adata.obsm["X_blanks"].sum(axis=1)
    adata.obs["misid_rate"] = (adata.obs["blank_counts"] / len(adata.uns["blank_names"])) / (
        adata.obs["total_counts"] / len(adata.var_names)
    )
    adata.obs["counts_per_volume"] = adata.obs["total_counts"] / adata.obs["volume"]
    if codebook:
        adata.varm["codebook"] = codebook.set_index("name").loc[adata.var_names].filter(like="bit").to_numpy()
        for bit in range(adata.varm["codebook"].shape[1]):
            adata.obs[f"bit{bit+1}"] = adata[:, adata.varm["codebook"][:, bit] == 1].X.sum(axis=1)
    if positions:
        adata.uns["fov_positions"] = positions.to_numpy()
    if name:
        adata.uns["dataset_name"] = name
    else:
        adata.uns["dataset_name"] = analysis.root.name
    return adata

def adjust_spatial_coordinates(adata, flip_horizontal=False, flip_vertical=False, transpose=False):
    if transpose and (flip_horizontal or flip_vertical):
        pass  # TODO: Should warn about order of operations
    if transpose:
        adata.obsm["X_spatial"] = np.roll(adata.obsm["X_spatial"], 1, 1)
    if flip_horizontal:
        adata.obsm["X_spatial"][:, 0] = -adata.obsm["X_spatial"][:, 0]
    if flip_vertical:
        adata.obsm["X_spatial"][:, 1] = -adata.obsm["X_spatial"][:, 1]

def normalize(adata, scale=False):
    sc.pp.normalize_total(adata)
    sc.pp.log1p(adata, base=2)
    adata.raw = adata
    if scale:
        sc.pp.scale(adata)
    return adata

def optimize_number_PCs(adata):
    def jumble(seq):
        return sample(list(seq), k=len(seq))

    mvars = []
    for i in tqdm(range(20), desc="Optimizing number of PCs"):
        randomized = adata.to_df().apply(jumble, axis=0)
        rndata = AnnData(randomized, dtype=adata.X.dtype)
        sc.tl.pca(rndata, svd_solver="arpack")
        mvars.append(rndata.uns["pca"]["variance"][0])

    cutoff = np.mean(mvars)
    sc.tl.pca(adata, svd_solver="arpack")
    n_pcs = int(np.sum(adata.uns["pca"]["variance"] > cutoff))
    stats.set("Number of PCs", n_pcs)
    return n_pcs

def cluster_cells(adata, n_pcs):
    if "pca" not in adata.uns:
        sc.tl.pca(adata, svd_solver="arpack")
    print("Calculating neighbors...", end="")
    sc.pp.neighbors(adata, n_pcs=n_pcs)
    print("done\nLeiden clustering...", end="")
    sc.tl.leiden(adata)
    print("done\nCalculating UMAP...", end="")
    sc.tl.paga(adata)
    sc.pl.paga(adata, plot=False)
    sc.tl.umap(adata, init_pos="paga", min_dist=0.3, spread=1)
    print("done")
    stats.set("Number of clusters", len(np.unique(adata.obs["leiden"])))

def find_cell_communities(adata: sc.AnnData, labels: str, radius: int = 150) -> None:
    """Group cells based on the cell types present in their vicinity."""
    ngraph = NearestNeighbors(radius=radius)
    ngraph.fit(adata.obsm["X_spatial"])
    _, indexes = ngraph.radius_neighbors(adata.obsm["X_spatial"])
    res = [pd.DataFrame(adata[cells, :].obs[labels].value_counts()) for cells in tqdm(indexes)]
    neighbors_table = pd.concat(res, axis=1)
    neighbors_table.columns = adata.obs_names
    neighbors_table = neighbors_table.fillna(0)
    adata.obsm["X_neighborhood"] = (neighbors_table / neighbors_table.sum(axis=0)).T.to_numpy()
    kmeans = KMeans().fit(adata.obsm["X_neighborhood"])
    adata.obs["community"] = kmeans.labels_.astype(str)

def transfer_labels(adata: sc.AnnData, refdata: sc.AnnData, label: str, **kwargs):
    common_genes = adata.var_names.intersection(refdata.var_names)
    classifier = KNeighborsClassifier(**kwargs)  # n_neighbors=5, n_jobs=24, metric='correlation')
    classifier.fit(refdata[:, common_genes].X, refdata.obs[label])
    return classifier.predict(adata[:, common_genes].X)

def label_clusters(adata: sc.AnnData, refdata: sc.AnnData, label: str, ref_label: str, number_sep=None):
    common_genes = adata.var_names.intersection(refdata.var_names)
    df = adata[:, common_genes].to_df()
    df["cluster"] = adata.obs[label]
    df = df.groupby("cluster").mean()
    df = df.apply(zscore, axis=0)
    df = df.dropna(axis=1)
    refdf = refdata[:, common_genes].to_df()
    refdf["cluster"] = refdata.obs[ref_label]
    refdf = refdf.groupby("cluster").mean()
    refdf = refdf.apply(zscore, axis=0)
    refdf = refdf.dropna(axis=1)
    common_genes = refdf.columns.intersection(df.columns)
    df = df[common_genes]
    refdf = refdf[common_genes]
    # Get all the correlations
    ps = []
    for _, row1 in df.iterrows():
        ps_ = []
        for _, row2 in refdf.iterrows():
            ps_.append(pearsonr(row1, row2)[0])
        ps.append(ps_)
    cordf = pd.DataFrame(ps, index=df.index, columns=refdf.index)
    mapping = cordf.idxmax(axis=1)
    if number_sep is not None:
        counts = mapping.value_counts()
        counter = {k: 1 for k in counts[counts > 1].index}
        for index, name in enumerate(mapping):
            if name in counter:
                mapping[index] = name + number_sep + str(counter[name])
                counter[name] += 1
    return mapping.loc[adata.obs[label]].values


from api.services.crud import EntranceAuth, EntranceAuthRegion, EntranceAuthUserCrud
from api.services.fetchapi import FetchAuthAPI
from utils.singleton import SingletonMeta
from utils.pyobjectid import PyObjectId

class EntranceAuthController(metaclass=SingletonMeta):
    def __init__(self):
        self.entranceAuthCrud = EntranceAuth()
        self.entranceAuthRegionCrud = EntranceAuthRegion()
        self.entranceAuthUserCrud = EntranceAuthUserCrud()
        self.fetchAuth = FetchAuthAPI()

    async def get_all_entrance_auth(self,page,limit):
        return await self.entranceAuthCrud.get_all(
            page=page,
            limit=limit,
            query={'enable':True},
            is_get_info=True
        )

    async def create_entrance_auth(self,data):
        new = await self.entranceAuthCrud.add(data)
        return new

    async def add_entrance_auth_region(self,data):
        new = await self.entranceAuthRegionCrud.add(data)
        return new

    async def delete_entrance_auth_region(self,id):
        await self.entranceAuthRegionCrud.delete(value=id)

    async def get_all_entrance_auth_region(self,page,limit):
        return await self.entranceAuthRegionCrud.get_all_entrance_auth_region(page, limit)

    async def add_entrance_auth_user(self,data):
        new = await self.entranceAuthUserCrud.add(data)
        return new

    async def delete_entrance_auth_user(self,id):
        await self.entranceAuthUserCrud.delete(value=id)

    async def get_all_entrance_auth_user(self,page,limit):
        data = await self.entranceAuthUserCrud.get_all_entrance_auth_user(page, limit)
        if data['list'] == []: return data
        ids_users = [str(info['id_user']) for info in data['list']]
        users = await self.fetchAuth.get_user_list(ids_users)
        user_dict = {}
        for user in users:
            user_dict[PyObjectId(user['_id'])] = user
        for i in range(len(data['list'])):
            id_user = data['list'][i].pop('id_user')
            data['list'][i]['user'] = user_dict[id_user]
        return data


from const import ROLES, ROLES_REVERSE, CHIPS, EMOJI_PREFIX
from data import emojis

def get_emoji(msg, role_name, scope="roles"):
    # 이모지 표기 문자열 추가(디스코드 서버에 파일명과 동일하게 추가 필요)
    if scope == "roles":
        return ''.join(["<:", ROLES_REVERSE[role_name["name"]], ":", str(emojis[msg.guild.id][''.join(
            [EMOJI_PREFIX, ROLES_REVERSE[role_name["name"]]])]), ">"])
    else:
        return ''.join(["<:", CHIPS[role_name], ":", str(emojis[msg.guild.id][CHIPS[role_name]]), ">"])

def get_explain(msg):
    return '\n'.join([
        "‣ 멀린팀",
        ' '.join([get_emoji(msg, ROLES["merlin"]), "멀린 : 모드레드를 제외한 악을 알고 있습니다."]),
        ' '.join([get_emoji(msg, ROLES["percival"]), "퍼시발 : 멀린/모르가나가 보이지만, 정체는 모릅니다."]),
        ' '.join(
            [get_emoji(msg, ROLES["guinevere"]), get_emoji(msg, ROLES["servant1"]),
             get_emoji(msg, ROLES["servant2"]), get_emoji(msg, ROLES["servant3"]),
             get_emoji(msg, ROLES["servant4"]), "선의 세력 : 원정을 성공 시키십시오."]),
        "",
        "‣ 모드레드팀",
        ' '.join([get_emoji(msg, ROLES["mordred"]), "모드레드 : 멀린에게 정체가 보이지 않습니다."]),
        ' '.join([get_emoji(msg, ROLES["morgana"]), "모르가나 : 퍼시발에게 멀린으로 위장합니다."]),
        ' '.join([get_emoji(msg, ROLES["assassin"]), "암살자 : 멀린을 암살할 수 있습니다."]),
        ' '.join([get_emoji(msg, ROLES["oberon"]), "오베론 : 악의 하수인과 서로 정체를 모릅니다."]),
        ' '.join(
            [get_emoji(msg, ROLES["minion1"]), get_emoji(msg, ROLES["minion2"]),
             get_emoji(msg, ROLES["minion3"]), "악의 하수인 : 원정을 실패 시키십시오."]),
        "",
        "‣ 제3세력",
        "▪ 랜슬롯 : 선/악 모두 존재하며, 원정 중, 팀을 배신할 수 있습니다.",
        "▪ 비비안 : 원정대원의 선/악을 알려줍니다.",
    ])

def get_status(msg, current_game):
    name = []
    desc = [": ".join(["‣ 원정상태", "원정중" if current_game.expedition else "모집중"])]
    for member in current_game.members:
        name.append(member.user.name)
    desc.append("\n‣ 원정설정")
    desc.append(": ".join(["••• 투표결과", "익명" if current_game.anonymous else "공개"]))
    if current_game.percival or current_game.mordred or current_game.oberon or current_game.lancelot:
        desc.append(": ".join(["••• 특수", " ".join(["퍼시발/모르가나" if current_game.percival else "",
                                                   "모드레드" if current_game.mordred else "",
                                                   "오베론" if current_game.oberon else "",
                                                   "랜슬롯" if current_game.lancelot else ""])]))
    if current_game.mordred or current_game.oberon:
        desc.append("••• 호수의 여신(비비안) 포함")
    desc.append(''.join(["\n‣ 원정구성", "(4라운드 실패 2장 필요)" if current_game.fourth_round else ""]))
    desc.append(''.join(["••• ", ", ".join(name)]))
    loyal_emoji = []
    evil_emoji = []
    for role_kr in current_game.roles["loyal"]:
        loyal_emoji.append(get_emoji(msg, role_kr))
    for role_kr in current_game.roles["evil"]:
        evil_emoji.append(get_emoji(msg, role_kr))
    desc.append(": ".join(["••• 멀린팀", ' '.join(loyal_emoji)]))
    desc.append(": ".join(["••• 모드레드팀", ' '.join(evil_emoji)]))
    rounds = []
    for _round in current_game.rounds:
        # 해당 라운드가 종료된 경우, 결과 이모지를 추가하고, 그 외에는 라운드별 인원수를 표기
        if current_game.rounds[_round]["terminate"]:
            if len(current_game.rounds[_round]["result"]["fail"]) > 0:
                rounds.append(get_emoji(msg, ''.join([EMOJI_PREFIX, "fail"]), "chips"))
            else:
                rounds.append(get_emoji(msg, ''.join([EMOJI_PREFIX, "success"]), "chips"))
        else:
            rounds.append(get_emoji(msg,
                                    ''.join([EMOJI_PREFIX,
                                             '_'.join([str(5 if len(current_game.members) < 5 else
                                                           8 if len(current_game.members) > 8 else
                                                           len(current_game.members)),
                                                       str(_round)])]), "chips"))
    desc.append(": ".join(["••• 라운드", ' '.join(rounds)]))
    deny_emoji = []
    if current_game.quest_round > 0:
        for _ in range(current_game.rounds[current_game.quest_round]["deny"]):
            deny_emoji.append(get_emoji(msg, CHIPS[''.join([EMOJI_PREFIX, "deny"])], "chips"))
        if len(deny_emoji) > 0:
            desc.append(": ".join(["••• 부결", ' '.join(deny_emoji)]))
        if current_game.quest_round > 1:
            if current_game.quest_round > 2 and (current_game.mordred or current_game.oberon):
                desc.append("\n‣ 호수의 여신")
                viviane_desc = []
                for member in current_game.viviane:
                    viviane_desc.append(member.user.name)
                desc.append(''.join(["••• ", " > ".join(viviane_desc)]))
            desc.append("\n‣ 원정결과")
            for i in range(1, 6):
                if current_game.rounds[i]["terminate"]:
                    success_count = len(current_game.rounds[i]["result"]["success"])
                    fail_count = len(current_game.rounds[i]["result"]["fail"])
                    round_desc = []
                    for j in range(success_count + fail_count):
                        if j < fail_count:
                            round_desc.append(get_emoji(msg, CHIPS[''.join([EMOJI_PREFIX, "quest_fail"])], "chips"))
                        else:
                            round_desc.append(get_emoji(msg, CHIPS[''.join([EMOJI_PREFIX, "quest_success"])], "chips"))
                    for member in current_game.rounds[i]["members"]:
                        round_desc.append(''.join([" ", member.user.name]))
                    desc.append(''.join([''.join(["••• ", str(i), "라운드: "]), ''.join(round_desc)]))
                else:
                    break
    if not current_game.expedition and current_game.quest_round > 0:
        desc.append("\n‣ 원정대 역할")
        member_roles = []
        for role_kr in current_game.roles["loyal"]:
            for member in current_game.members:
                if member.role == role_kr:
                    member_roles.append(": ".join([''.join(["••• ", get_emoji(msg, member.role)]), member.user.name]))
                    break
        for role_kr in current_game.roles["evil"]:
            for member in current_game.members:
                if member.role == role_kr:
                    member_roles.append(": ".join([''.join(["••• ", get_emoji(msg, member.role)]), member.user.name]))
                    break
        desc.append('\n'.join(member_roles))
    return '\n'.join(desc)

def get_role(current_game, member):
    desc = []
    if member.role == ROLES["merlin"]:
        desc.append("모드레드를 제외한 악의 하수인들을 알고 있지만, 암살자를 조심하세요.")
        desc.append("‣ 모드레드를 제외한 악의 하수인" if current_game.mordred else "‣ 악의 하수인")
        for evils in current_game.members:
            if "evil" == evils.role["lawful"] and evils.role["name"] not in "모드레드":
                desc.append(''.join(["••• ", evils.user.name]))
    elif member.role == ROLES["percival"]:
        desc.append("악의 하수인에게 속지 말고, 멀린을 찾아 원정을 성공 시키십시오.")
        desc.append("‣ 멀린 혹은 모르가나")
        for merlins in current_game.members:
            if merlins.role in (ROLES["merlin"], ROLES["morgana"]):
                desc.append(''.join(["••• ", merlins.user.name]))
    elif member.role == ROLES["assassin"]:
        desc.append("선의 세력을 속여 원정을 실패 시키십시오. 원정이 성공하는 경우, 멀린을 암살하여 악의 세력을 승리 시키십시오.")
        desc.append("‣ 오베론을 제외한 악의 하수인" if current_game.oberon else "‣ 악의 하수인")
        for evils in current_game.members:
            if "evil" == evils.role["lawful"] and evils.role["name"] not in "오베론":
                desc.append(''.join(["••• ", evils.user.name]))
    elif member.role == ROLES["morgana"]:
        desc.append("퍼시발에게 멀린으로 위장하였습니다. 퍼시발을 현혹시키고 원정을 실패 시키십시오.")
        desc.append("‣ 오베론을 제외한 악의 하수인" if current_game.oberon else "‣ 악의 하수인")
        for evils in current_game.members:
            if "evil" == evils.role["lawful"] and evils.role["name"] not in "오베론":
                desc.append(''.join(["••• ", evils.user.name]))
    elif member.role == ROLES["mordred"]:
        desc.append("멀린에게 정체를 들키지 않았습니다. 선의 세력 사이에 숨어 원정을 실패 시키십시오.")
        desc.append("‣ 오베론을 제외한 악의 하수인" if current_game.oberon else "‣ 악의 하수인")
        for evils in current_game.members:
            if "evil" == evils.role["lawful"] and evils.role["name"] not in "오베론":
                desc.append(''.join(["••• ", evils.user.name]))
    elif member.role == ROLES["oberon"]:
        desc.append("다른 악을 알 수 없습니다. 악의 하수인을 찾고, 선의 세력을 속여 원정을 실패 시키십시오.")
    elif member.role in (ROLES["lancelot_loyal"], ROLES["lancelot_evil"]):
        desc.append("선/악 모두 될 수 있습니다. 배신의 기회를 노리세요.")
        if member.role == ROLES["lancelot_evil"]:
            desc.append("‣ 오베론을 제외한 악의 하수인" if current_game.oberon else "‣ 악의 하수인")
            for evils in current_game.members:
                if "evil" == evils.role["lawful"] and evils.role["name"] not in "오베론":
                    desc.append(''.join(["••• ", evils.user.name]))
    elif member.role in (ROLES["guinevere"], ROLES["servant1"], ROLES["servant2"], ROLES["servant3"],
                         ROLES["servant4"]):
        desc.append("악의 하수인에게 속지 말고 원정을 성공 시키십시오.")
    elif member.role in (ROLES["minion1"], ROLES["minion2"], ROLES["minion3"]):
        desc.append("선의 세력을 속여 원정을 실패 시키십시오.")
        desc.append("‣ 오베론을 제외한 악의 하수인" if current_game.oberon else "‣ 악의 하수인")
        for evils in current_game.members:
            if "evil" == evils.role["lawful"] and evils.role["name"] not in "오베론":
                desc.append(''.join(["••• ", evils.user.name]))
    return '\n'.join(desc)


#!/usr/bin/env python
# **************************************************************************
# *
# * Authors:     J.M. De la Rosa Trevin (delarosatrevin@scilifelab.se) [1]
# *
# * [1] SciLifeLab, Stockholm University
# *
# * 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 2 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, write to the Free Software
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
# * 02111-1307  USA
# *
# *  All comments concerning this program package may be sent to the
# *  e-mail address 'scipion@cnb.csic.es'
# *
# **************************************************************************
from __future__ import print_function

import sys
from os.path import join, exists, dirname
import importlib
import inspect
import traceback
from collections import OrderedDict
from future.utils import iteritems

from pyworkflow.em import Domain
from pyworkflow.protocol import Protocol
import pyworkflow.em as em
import pyworkflow.utils as pwutils

from plugin_funcs import PluginInfo

exitWithErrors = False

def usage(error):
    print("""
    ERROR: %s

    Usage: scipion python scripts/inspect-plugins.py [PLUGIN-NAME] [info] [--showBase]
        This script loads all Scipion plugins found.
        If a PLUGIN-NAME is passed, it will inspect that plugin
        in more detail.
        'info' argument will print plugin summary,
        '-showBase' will print Base class protocols (hidden by default).
    """ % error)
    sys.exit(1)

def getSubmodule(plugin, name, subname):
    """ Return a tuple: (module, error)
    If module is None:
        1) if error is None is that the submodule does not exist
        2) if error is not None, it is an Exception raised when
        importing the submodule
    """
    try:
        m = importlib.import_module('%s.%s' % (name, subname))
        r = (m, None)
    except Exception as e:
        noModuleMsg = 'No module named %s' % subname
        msg = str(e)
        moduleExists = (exists(join(dirname(plugin.__file__), "%s.py" % subName)) or
                        exists(join(dirname(plugin.__file__), subName)))
        r = (None, None if msg == noModuleMsg and not moduleExists else traceback.format_exc())
    return r

def getFirstLine(doc):
    """ Get the first non empty line from doc. """
    if doc:
        for lines in doc.split('\n'):
            l = lines.strip()
            if l:
                return l
    return ''

n = len(sys.argv)

if n > 4:
    usage("Incorrect number of input parameters")

if n == 1:  # List all plugins
    plugins = Domain.getPlugins()
    print("Plugins:")
    for k, v in iteritems(plugins):
        print("-", k)

    print("Objects")
    pwutils.prettyDict(Domain.getObjects())

    print("Protocols")
    pwutils.prettyDict(Domain.getProtocols())

    print("Viewers")
    pwutils.prettyDict(Domain.getViewers())

elif n == 2:
    if sys.argv[1] in ['-h', '--help', 'help']:
        usage("Printing help message")

    pluginName = sys.argv[1]
    plugin = Domain.getPlugin(pluginName)
    print("Plugin: %s" % pluginName)
    for subName in ['constants', 'convert', 'protocols',
                    'wizards', 'viewers', 'tests']:
        sub, error = getSubmodule(plugin, pluginName, subName)

        if sub is None:
            if error is None:
                msg = " missing"
            else:
                exitWithErrors = True
                msg = " error -> %s" % error

        else:
            msg = " loaded"

        print("   >>> %s: %s" % (subName, msg))

elif n > 2:
    if sys.argv[2] == 'info':
        pluginName = sys.argv[1]
        showBase = True if (n == 4 and sys.argv[3] == '--showBase') else False
        subclasses = {}
        emCategories = [('Imports', em.ProtImport),
                        ('Micrographs', em.ProtMicrographs),
                        ('Particles', em.ProtParticles),
                        ('2D', em.Prot2D),
                        ('3D', em.Prot3D)]

        plugin = Domain.getPlugin(pluginName)
        version = PluginInfo('scipion-em-%s' % pluginName).pipVersion
        bin = PluginInfo('scipion-em-%s' % pluginName).printBinInfoStr()
        print("Plugin name: %s, version: %s" % (pluginName, version))
        print("Plugin binaries: %s" % bin)

        # print bibtex
        bib, error2 = getSubmodule(plugin, pluginName, 'bibtex')
        if bib is None:
            if error2 is None:
                msg = " missing bibtex"
            else:
                exitWithErrors = True
                msg = " error -> %s" % error2
        else:
            print("Plugin references:")
            bibtex = pwutils.parseBibTex(bib.__doc__)

            for citeStr in bibtex:
                text = Protocol()._getCiteText(bibtex[citeStr])
                print(text)

        # print protocols
        sub, error = getSubmodule(plugin, pluginName, 'protocols')
        if sub is None:
            if error is None:
                msg = " missing protocols"
            else:
                exitWithErrors = True
                msg = " error -> %s" % error

        else:
            for name in dir(sub):
                attr = getattr(sub, name)
                if inspect.isclass(attr) and issubclass(attr, Protocol):
                    # Set this special property used by Scipion
                    attr._package = plugin
                    subclasses[name] = attr

        print("Plugin protocols:\n")
        print("%-35s %-35s %-10s %-s" % (
            'NAME', 'LABEL', 'CATEGORY', 'DESCRIPTION'))

        prots = OrderedDict(sorted(subclasses.items()))
        for prot in prots:
            label = prots[prot].getClassLabel()
            desc = getFirstLine(prots[prot].__doc__)
            cat = 'None'

            for c in emCategories:
                if issubclass(prots[prot], c[1]):
                    cat = c[0]
                if prots[prot].isBase():
                    cat = 'Base prot'

            # skip Base protocols if not requested
            if prots[prot].isBase() and not showBase:
                continue
            else:
                print("%-35s %-35s %-10s %-s" % (prot, label, cat, desc))

    else:
        usage("The last argument must be 'info'")

if exitWithErrors:
    sys.exit(1)
else:
    sys.exit(0)


from __future__ import print_function
from __future__ import absolute_import

import subprocess
import unittest

import greenlet
from . import _test_extension_cpp
from . import TestCase
from . import WIN

class CPPTests(TestCase):
    def test_exception_switch(self):
        greenlets = []
        for i in range(4):
            g = greenlet.greenlet(_test_extension_cpp.test_exception_switch)
            g.switch(i)
            greenlets.append(g)
        for i, g in enumerate(greenlets):
            self.assertEqual(g.switch(), i)

    def _do_test_unhandled_exception(self, target):
        import os
        import sys
        script = os.path.join(
            os.path.dirname(__file__),
            'fail_cpp_exception.py',
        )
        args = [sys.executable, script, target.__name__ if not isinstance(target, str) else target]
        __traceback_info__ = args
        with self.assertRaises(subprocess.CalledProcessError) as exc:
            subprocess.check_output(
                args,
                encoding='utf-8',
                stderr=subprocess.STDOUT
            )

        ex = exc.exception
        expected_exit = self.get_expected_returncodes_for_aborted_process()
        self.assertIn(ex.returncode, expected_exit)
        self.assertIn('fail_cpp_exception is running', ex.output)
        return ex.output

    def test_unhandled_nonstd_exception_aborts(self):
        # verify that plain unhandled throw aborts
        self._do_test_unhandled_exception(_test_extension_cpp.test_exception_throw_nonstd)

    def test_unhandled_std_exception_aborts(self):
        # verify that plain unhandled throw aborts
        self._do_test_unhandled_exception(_test_extension_cpp.test_exception_throw_std)

    @unittest.skipIf(WIN, "XXX: This does not crash on Windows")
    # Meaning the exception is getting lost somewhere...
    def test_unhandled_std_exception_as_greenlet_function_aborts(self):
        # verify that plain unhandled throw aborts
        output = self._do_test_unhandled_exception('run_as_greenlet_target')
        self.assertIn(
            'greenlet: Unhandled C++ exception: Thrown from an extension.',
            output
        )

    def test_unhandled_exception_in_greenlet_aborts(self):
        # verify that unhandled throw called in greenlet aborts too
        self._do_test_unhandled_exception('run_unhandled_exception_in_greenlet_aborts')

if __name__ == '__main__':
    unittest.main()


import unittest
import doctest
import datetime

from zope.traversing.testing import setUp

class TestHelpers(unittest.TestCase):

    # parse_date has a doctest

    def test_parse_datetime(self):
        from schooltool.common import parse_datetime
        dt = datetime.datetime
        valid_dates = (
            ("2000-01-01 00:00:00", dt(2000, 1, 1, 0, 0, 0, 0)),
            ("2000-01-01 00:00:00.000000", dt(2000, 1, 1, 0, 0, 0, 0)),
            ("2000-01-01T00:00:00", dt(2000, 1, 1, 0, 0, 0, 0)),
            ("2005-12-23 11:22:33", dt(2005, 12, 23, 11, 22, 33)),
            ("2005-12-23T11:22:33", dt(2005, 12, 23, 11, 22, 33)),
            ("2005-12-23T11:22:33.4", dt(2005, 12, 23, 11, 22, 33, 400000)),
            ("2005-12-23T11:22:33.456789", dt(2005, 12, 23, 11, 22, 33,
                                              456789)),
        )
        for s, d in valid_dates:
            result = parse_datetime(s)
            self.assertEquals(result, d,
                              "parse_datetime(%r) returned %r" % (s, result))
        invalid_dates = (
            "2000/01/01",
            "2100-02-29 00:00:00",
            "2005-12-23 11:22:33 "
        )
        for s in invalid_dates:
            try:
                result = parse_datetime(s)
            except ValueError:
                pass
            else:
                self.fail("parse_datetime(%r) did not raise" % s)

def test_suite():
    optionflags = (doctest.ELLIPSIS
                   | doctest.REPORT_NDIFF
                   | doctest.NORMALIZE_WHITESPACE)
    suite = unittest.TestSuite()
    suite.addTest(doctest.DocTestSuite('schooltool.common',
                                       optionflags=optionflags))
    suite.addTest(doctest.DocTestSuite('schooltool.common.inlinept',
                                       optionflags=optionflags,
                                       setUp=lambda test: setUp()))
    suite.addTest(unittest.makeSuite(TestHelpers))
    return suite

if __name__ == '__main__':
    unittest.main(defaultTest='test_suite')