"""
Parameters
----------

General
+++++++

   input : str, default=''
      (If smi is None) Optionally, file containing the SMILES strings and
      names of the molecules. Current file extensions: .smi, .sdf, .cdx,
      .csv, .com, .gjf, .mol, .mol2, .xyz, .txt, .yaml, .yml, .rtf
      For .csv files (i.e. FILENAME.csv), two columns are required,
      'code_name' with the names and 'SMILES' for the SMILES string
   program : str, default=None
      Program required in the conformational sampling.
      Current options: 'rdkit', 'summ', 'fullmonte', 'crest'
   smi : str, default=None
      Optionally, define a SMILES string as input
   name : str, default=None
      (If smi is defined) optionally, define a name for the system
   w_dir_main : str, default=os.getcwd()
      Working directory
   destination : str, default=None,
     Directory to create the output file(s)
   varfile : str, default=None
      Option to parse the variables using a yaml file (specify the filename)
   max_workers : int, default=4
      Number of simultaneous RDKit jobs run with multiprocessing
      (WARNING! More than 12 simultaneous jobs might collapse your computer!)
   charge : int, default=None
      Charge of the calculations used in the following input files.
      If charge isn't defined, it automatically reads the charge of the
      SMILES string
   mult : int, default=None
      Multiplicity of the calculations used in the following input files. If
      mult isn't defined, it automatically reads the multiplicity of the mol
      object created with the SMILES string. Be careful with the automated
      calculation of mult from mol objects when using metals!
   prefix : str, default=''
      Prefix added to all the names
   suffix : str, default=''
      Suffix added to all the names
   stacksize : str, default='1G'
      Controls the stack size used (especially relevant for xTB/CREST
      calculations of large systems, where high stack sizes are needed)

General RDKit-based
+++++++++++++++++++

   sample : int, default='auto'
      Number of conformers used initially in the RDKit sampling. If this option
      isn't specified, AQME automatically calculates (previously benchmarked) an
      approximate number based on number of rotatable bonds, XH (i.e. OH) groups,
      saturated cycles, etc (see the auto_sampling() function in csearch.py for
      more information)
   auto_sample : int, default=20
      Base multiplicator number used in the sample option
   ff : str, default='MMFF'
      Force field used in RDKit optimizations and energy calculations. Current
      options: MMFF and UFF (if MMFF fails, AQME tries to use UFF automatically)
   ewin_csearch : float, default=5.0
      Energy window in kcal/mol to discard conformers (i.e. if a conformer is
      more than the E window compared to the most stable conformer)
   initial_energy_threshold : float, default=0.0001
      Energy difference in kcal/mol between unique conformers for the first
      filter of only E
   energy_threshold : float, default=0.25
      Energy difference in kcal/mol between unique conformers for the second
      filter of E + RMS
   rms_threshold : float, default=0.25
      RMS difference between unique conformers for the second filter of E + RMS
   opt_steps_rdkit : int, default=1000
      Max cycles used in RDKit optimizations
   heavyonly : bool, default=True
      Only consider heavy atoms during RMS calculations for filtering (in the
      Chem.rdMolAlign.GetBestRMS() RDKit function)
   max_matches_rmsd : int, default=1000
      Max matches during RMS calculations for filtering (maxMatches option in
      the Chem.rdMolAlign.GetBestRMS() RDKit function)
   max_mol_wt : int, default=0
      Discard systems with molecular weights higher than this parameter
      (in g/mol). If 0 is set, this filter is off
   max_torsions : int, default=0
      Discard systems with more than this many torsions (relevant to avoid
      molecules with many rotatable bonds). If 0 is set, this filter is off
   seed : int, default=62609
      Random seed used during RDKit embedding (in the
      Chem.rdDistGeom.EmbedMultipleConfs() RDKit function)
   geom : list, default=[]
      Geometry rule to pass for the systems. Format: [SMARTS,VALUE]. Geometry rules
      might be atoms, bonds, angles and dihedral. For example, a rule to keep only
      molecules with C-Pd-C atoms at 180 degrees: ['[C][Pd][C]',180].
      Special rules (--geom ['RULE_NAME']):
        1. ['Ir_squareplanar']
   bond_thres : float, default=0.2
      Threshold used to discard bonds in the geom option (+-0.2 A)
   angle_thres : float, default=30
      Threshold used to discard angles in the geom option (+-30 degrees)
   dihedral_thres : float, default=30
      Threshold used to discard dihedral angles in the geom option (+-30 degrees)

Only organometallic molecules
.............................

   auto_metal_atoms : bool, default=True
     Automatically detect metal atoms for the RDKit conformer generation. Charge
     and mult should be specified as well since the automatic charge and mult
     detection might not be precise.
   complex_type : str, default=''
      Forces the metal complexes to adopt a predefined geometry. This option is
      especially relevant when RDKit predicts wrong complex geometries or gives
      a mixture of geometries. Current options: squareplanar, squarepyramidal,
      linear, trigonalplanar

SUMM only
+++++++++

   degree : float, default=120.0
      Interval of degrees to rotate dihedral angles during SUMM sampling
      (i.e. 120.0 would create 3 conformers for each dihedral, at 0,
      120 and 240 degrees)

Fullmonte only
++++++++++++++

   ewin_fullmonte : float, default=5.0
      Energy window in kcal/mol to discard conformers (i.e. if a conformer is
      more than the E window compared to the most stable conformer)
   ewin_sample_fullmonte : float, default=2.0
      Energy window in kcal/mol to use conformers during the Fullmonte sampling
      (i.e. conformers inside the E window compared to the most stable conformer
      are considered as unique in each step of the sampling)
   nsteps_fullmonte : int, default=100
      Number of steps (or conformer batches) to carry during the Fullmonte
      sampling
   nrot_fullmonte : int, default=3
      Number of dihedrals to rotate simultaneously (picked at random) during
      each step of the Fullmonte sampling
   ang_fullmonte : float, default=30
      Available angle interval to use in the Fullmonte sampling. For example, if
      the angle is 120.0, the program chooses randomly between 120 and 240
      degrees (picked at random) during each step of the sampling

CREST only
++++++++++

   nprocs : int, default=2
      Number of processors used in CREST optimizations
   constraints_atoms : list, default=[]
      Specify constrained atoms as [AT1,AT2,AT3]. An example of multiple constraints with
      atoms 1, 2 and 5 frozen: [1,2,5]
   constraints_dist : list of lists, default=[]
      Specify distance constraints as [AT1,AT2,DIST]. An example of multiple constraints with
      atoms 1 and 2 frozen at a distance of 1.8 Å, and atoms 4 and 5 with distance of 2.0 Å:
      [[1,2,1.8],[4,5,2.0]]
   constraints_angle : list of lists, default=[]
      Specify angle constraints as [AT1,AT2,AT3,ANGLE]. An example of multiple constraints with
      atoms 1, 2 and 3 frozen at an angle of 180 degrees, and atoms 4, 5 and 6 with an angle of 120:
      [[1,2,3,180],[4,5,6,120]]
   constraints_dihedral : list of lists, default=[]
      Specify dihedral constraints as [AT1,AT2,AT3,AT4,DIHEDRAL]. An example of multiple constraints
      with atoms 1, 2, 3 and 4 frozen at a dihedral angle of 180 degrees, and atoms 4, 5, 6 and 7
      with a dihedral angle of 120: [[1,2,3,4,180],[4,5,6,7,120]]
   crest_force : float, default=0.5
      Force constant for constraints in the .xcontrol.sample file for CREST jobs
   crest_keywords : str, default=None
      Define additional keywords to use in CREST that are not included in --chrg,
      --uhf, -T and -cinp. For example: '--alpb ch2cl2 --nci --cbonds 0.5'
   cregen : bool, default=False
      If True, perform a CREGEN analysis after CREST (filtering options below)
   cregen_keywords : str, default=None
      Additional keywords for CREGEN (i.e. cregen_keywords='--ethr 0.02')
   xtb_keywords : str, default=None
      Define additional keywords to use in the xTB pre-optimization that are not
      included in -c, --uhf, -P and --input. For example: '--alpb ch2cl2 --gfn 1'
    crest_nrun : int, default=1
      Specify as number of runs if multiple starting points from RDKit starting points is required.
"""
#####################################################.
#          This file stores the CSEARCH class       #
#             used in conformer generation          #
#####################################################.

import math
import os
import sys
import time
import shutil
import subprocess
import glob
from pathlib import Path
import pandas as pd
import concurrent.futures as futures
import multiprocessing as mp
from progress.bar import IncrementalBar
import numpy as np

from rdkit.Chem import AllChem as Chem
from rdkit.Chem import Descriptors as Descriptors
from rdkit.Chem import rdmolfiles
from rdkit.Chem import rdMolTransforms, PropertyMol, rdDistGeom, Lipinski

from aqme.filter import (
    filters, ewin_filter,
    pre_E_filter,
    RMSD_and_E_filter,
    geom_filter
    )
from aqme.csearch.utils import (
    prepare_direct_smi,
    prepare_smiles_files,
    prepare_csv_files,
    prepare_cdx_files,
    prepare_com_files,
    prepare_sdf_files,
    prepare_pdb_files,
    creation_of_dup_csv_csearch,
    minimize_rdkit_energy,
    com_2_xyz,
    check_constraints,
    smi_to_mol,
    getDihedralMatches,
    cluster_conformers
    )
from aqme.csearch.templates import template_embed, check_metal_neigh
from aqme.csearch.fullmonte import generating_conformations_fullmonte, realign_mol
from aqme.utils import (
    substituted_mol,
    load_variables,
    set_metal_atomic_number,
    check_xtb,
    check_crest,
    get_files
    )
from aqme.csearch.crest import xtb_opt_main

class csearch:
    """
    Class absracting the geometry generation and conformational search procedure.
    For further detail on the currently accepted keyword arguments (kwargs)
    please look at the Parameters section (in the module documentation).
    """

    def __init__(self, **kwargs):

        start_time_overall = time.time()
        # load default and user-specified variables
        self.args = load_variables(kwargs, "csearch")

        csearch_program = True
        if self.args.program is None:
            csearch_program = False
        if csearch_program:
            if self.args.program.lower() not in ["rdkit", "summ", "fullmonte", "crest"]:
                csearch_program = False
        if not csearch_program:
            self.args.log.write('\nx  Program not specified or not supported for CSEARCH! Specify: program="rdkit" (or "crest", "summ", "fullmonte")')
            self.args.log.finalize()
            sys.exit()

        if str(self.args.auto_metal_atoms) == "False":
            self.args.auto_metal_atoms = False

        if self.args.program.lower() == "crest":
            _ = check_xtb(self)
            _ = check_crest(self)

        if self.args.smi is None and self.args.input == "":
            self.args.log.write("\nx  Program requires either a SMILES or an input file to proceed! Please look up acceptable file formats. Specify: smi='CCC' (or input='filename.csv')")
            self.args.log.finalize()
            sys.exit()
        elif self.args.smi is not None and self.args.input != "":
            self.args.log.write("\nx  Program requires either a SMILES or an input file to proceed, don't use both!")
            self.args.log.finalize()
            sys.exit()
        # specify a dummy extension for inputif smi is not None (avoids errors in LOG printings)
        if self.args.smi is not None:
            self.args.input = 'no_ext.no_ext'

        try:
            if Path(f"{self.args.w_dir_main}").exists():
                os.chdir(self.args.w_dir_main)
        except FileNotFoundError:
            self.args.w_dir_main = Path(f"{os.getcwd()}/{self.args.w_dir_main}")
            os.chdir(self.args.w_dir_main)

        # load files from AQME input
        if self.args.smi is not None:
            csearch_files = [self.args.name]
        else:
            csearch_files = get_files(self.args.input)
            if len(csearch_files) == 0:
                self.args.log.write(f"\nx  Input file ({self.args.input}) not found!")
                self.args.log.finalize()
                sys.exit()

        for csearch_file in csearch_files:
            # load jobs for conformer generation
            if self.args.smi is not None:
                job_inputs = prepare_direct_smi(self.args)

            else:
                job_inputs = self.load_jobs(csearch_file)

            self.args.log.write(f"\nStarting CSEARCH with {len(job_inputs)} job(s) (SDF, XYZ, CSV, etc. files might contain multiple jobs/structures inside)\n")

            # runs the conformer sampling with multiprocessors
            self.run_csearch(job_inputs)

            # store all the information into a CSV file
            csearch_file_no_path = (
                os.path.basename(Path(csearch_file)).split(".")[0]
            )
            self.csearch_csv_file = self.args.w_dir_main.joinpath(
                f"CSEARCH-Data-{csearch_file_no_path}.csv"
            )
            if self.args.verbose:
                self.final_dup_data.to_csv(self.csearch_csv_file, index=False)

            # removes systems that did not generate any conformers
            for sdf_file in glob.glob(f'{self.args.w_dir_main}/CSEARCH/*.sdf'):
                if os.path.getsize(sdf_file) == 0:
                    os.remove(sdf_file)

        elapsed_time = round(time.time() - start_time_overall, 2)
        self.args.log.write(f"\nTime CSEARCH: {elapsed_time} seconds\n")
        self.args.log.finalize()

        # this is added to avoid path problems in jupyter notebooks
        os.chdir(self.args.initial_dir)

    def load_jobs(self, csearch_file):
        """
        Load information of the different molecules for conformer generation
        """

        SUPPORTED_INPUTS = [
            "smi",
            "sdf",
            "cdx",
            "csv",
            "com",
            "gjf",
            "mol",
            "mol2",
            "xyz",
            "txt",
            "yaml",
            "yml",
            "rtf",
            "pdb",
        ]

        file_format = os.path.basename(Path(csearch_file)).split('.')[1]
        # Checks
        if file_format.lower() not in SUPPORTED_INPUTS:
            self.args.log.write("\nx  Input filetype not currently supported!")
            self.args.log.finalize()
            sys.exit()

        smi_derivatives = ["smi", "txt", "yaml", "yml", "rtf"]
        Extension2inputgen = dict()
        for key in smi_derivatives:
            Extension2inputgen[key] = prepare_smiles_files
        Extension2inputgen["csv"] = prepare_csv_files
        Extension2inputgen["cdx"] = prepare_cdx_files
        Extension2inputgen["gjf"] = prepare_com_files
        Extension2inputgen["com"] = prepare_com_files
        Extension2inputgen["xyz"] = prepare_com_files
        Extension2inputgen["sdf"] = prepare_sdf_files
        Extension2inputgen["mol"] = prepare_sdf_files
        Extension2inputgen["mol2"] = prepare_sdf_files
        Extension2inputgen["pdb"] = prepare_pdb_files

        # Prepare the jobs
        prepare_function = Extension2inputgen[file_format]
        try:
            job_inputs = prepare_function(self.args, csearch_file)
        except FileNotFoundError:
            self.args.log.write(f'\nx  File {os.path.basename(csearch_file)} was not found! In the "input" option, make sure that 1) the PATH to the files is correct and 2) the PATH doesn\'t start with "/".')
            self.args.log.finalize()
            sys.exit()

        return job_inputs

    def run_csearch(self, job_inputs):
        # create the dataframe to store the data
        self.final_dup_data = creation_of_dup_csv_csearch(self.args.program.lower())

        bar = IncrementalBar(
            "o  Number of finished jobs from CSEARCH", max=len(job_inputs)
        )
        with futures.ProcessPoolExecutor(
            max_workers=self.args.max_workers, mp_context=mp.get_context("spawn")
        ) as executor:
            # Submit a set of asynchronous jobs
            jobs = []
            # Submit the Jobs
            for job_input in job_inputs:
                (
                    smi_,
                    name_,
                    charge_,
                    mult_,
                    constraints_atoms_,
                    constraints_dist_,
                    constraints_angle_,
                    constraints_dihedral_,
                    complex_type_,
                    geom_
                ) = job_input
                job = executor.submit(
                    self.compute_confs(
                        smi_,
                        name_,
                        charge_,
                        mult_,
                        constraints_atoms_,
                        constraints_dist_,
                        constraints_angle_,
                        constraints_dihedral_,
                        complex_type_,
                        geom_
                    )
                )
                jobs.append(job)

                bar.next()

            bar.finish()

    def compute_confs(
        self,
        smi,
        name,
        charge,
        mult,
        constraints_atoms,
        constraints_dist,
        constraints_angle,
        constraints_dihedral,
        complex_type,
        geom
    ):
        """
        Function to start conformer generation
        """

        self.args.log.write(f"\n   ----- {os.path.basename(Path(name))} -----")

        if self.args.smi is not None or os.path.basename(Path(self.args.input)).split(".")[1] in ["smi","csv","cdx","txt","yaml","yml","rtf"]:
            (
                mol,
                constraints_atoms,
                constraints_dist,
                constraints_angle,
                constraints_dihedral,
                complex_ts
            ) = smi_to_mol(
                smi,
                self.args.program.lower(),
                self.args.log,
                self.args.seed,
                constraints_atoms,
                constraints_dist,
                constraints_angle,
                constraints_dihedral,
            )
            if mol is None:
                self.args.log.write(f"\nx  Failed to convert the provided SMILES ({smi}) to an RDkit Mol object! Please check the starting smiles.")
                # if a list of SMILES is provided, the program doesn't stop if one SMILES fails to convert to mol
                if os.path.basename(Path(self.args.input)).split(".")[1] not in ["csv","cdx","txt","yaml","yml","rtf"]:
                    self.args.log.finalize()
                    sys.exit()
                return

        else:
            # for 3D input formats, the smi variable represents the mol object
            mol = smi
            if mol is None:
                self.args.log.write(f"\nx  Failed to convert the provided input to an RDkit Mol object! Please check the starting structure.")
                if os.path.basename(Path(self.args.input)).split(".")[1] not in ["csv","cdx","txt","yaml","yml","rtf"]:
                    self.args.log.finalize()
                    sys.exit()
                return

            # check if the optimization is constrained
            complex_ts = check_constraints(self)

        if self.args.destination is None:
            self.csearch_folder = Path(self.args.initial_dir).joinpath(
                f"CSEARCH"
            )
        else:
            if self.args.initial_dir.as_posix() in f"{self.args.destination}":
                self.csearch_folder = Path(self.args.destination)
            else:
                self.csearch_folder = Path(self.args.initial_dir).joinpath(self.args.destination)

        self.csearch_folder.mkdir(exist_ok=True, parents=True)

        # for 3D input types
        if self.args.program.lower() in ["crest"] and self.args.smi is None:
            if os.path.basename(Path(self.args.input)).split(".")[1] in ["pdb", "mol2", "mol", "sdf"]:
                command_pdb = [
                    "obabel",
                    f'-i{os.path.basename(Path(self.args.input)).split(".")[1]}',
                    f'{name}.{os.path.basename(Path(self.args.input)).split(".")[1]}',
                    "-oxyz",
                    f"-O{os.path.dirname(Path(name))}/{os.path.basename(Path(name)).split('.')[0]}_{self.args.program.lower()}.xyz",
                ]
                subprocess.run(
                    command_pdb,
                    stdout=subprocess.DEVNULL,
                    stderr=subprocess.DEVNULL,
                )
            elif os.path.basename(Path(self.args.input)).split(".")[1] in ["gjf", "com"]:
                xyz_file, _, _ = com_2_xyz(f'{name}.{os.path.basename(Path(self.args.input)).split(".")[1]}')
                shutil.move(xyz_file, f"{name}_{self.args.program.lower()}.xyz")
            elif os.path.basename(Path(self.args.input)).split(".")[1] == "xyz":
                shutil.copy(f"{name}.xyz", f"{name}_{self.args.program.lower()}.xyz")

        template_opt = False

        # detects metal atoms
        if self.args.auto_metal_atoms:
            _ = self.find_metal_atom(mol,charge,mult)

        # replaces the metal for an I atom
        if len(self.args.metal_atoms) >= 1:
            (
                self.args.metal_idx,
                self.args.complex_coord,
                self.args.metal_sym,
            ) = substituted_mol(self, mol, "I")

            # get pre-determined geometries for metal complexes
            accepted_complex_types = [
                "squareplanar",
                "squarepyramidal",
                "linear",
                "trigonalplanar",
            ]
            if complex_type != '' and complex_type not in accepted_complex_types:
                self.args.log.write(f"x  The metal template specified in complex_type ({complex_type}) is not valid! Options: squareplanar, squarepyramidal, linear and trigonalplanar")
                if os.path.basename(Path(self.args.input)).split(".")[1] not in ["csv","cdx","txt","yaml","yml","rtf"]:
                    self.args.log.finalize()
                    sys.exit()
                return

            if complex_type in accepted_complex_types:
                count_metals = 0
                valid_template = True
                # check if the specified metal is included in the system
                for metal_idx_ind in self.args.metal_idx:
                    if metal_idx_ind is not None:
                        # calculate number of expected neighbours
                        valid_template = check_metal_neigh(mol, complex_type, metal_idx_ind, self.args.log, valid_template)
                        count_metals += 1

                if count_metals == 1 and valid_template:
                    template_opt = True
                    template_kwargs = dict()
                    template_kwargs["complex_type"] = complex_type
                    template_kwargs["metal_idx"] = self.args.metal_idx
                    template_kwargs["maxsteps"] = self.args.opt_steps_rdkit
                    template_kwargs["heavyonly"] = self.args.heavyonly
                    template_kwargs["maxmatches"] = self.args.max_matches_rmsd
                    template_kwargs["mol"] = mol
                    template_kwargs["name"] = name
                    template_kwargs["geom"] = geom
                    items = template_embed(self, **template_kwargs)

                    total_data = creation_of_dup_csv_csearch(self.args.program.lower())

                    for mol_obj, name_in, coord_map, alg_map, template, original_atn in zip(*items):
                        data = self.conformer_generation(
                            mol_obj,
                            name_in,
                            constraints_atoms,
                            constraints_dist,
                            constraints_angle,
                            constraints_dihedral,
                            complex_ts,
                            charge,
                            mult,
                            smi,
                            geom,
                            coord_map,
                            alg_map,
                            template,
                            original_atn
                        )
                        frames = [total_data, data]
                        total_data = pd.concat(frames, sort=True)

                elif count_metals > 1 or count_metals == 0:
                    self.args.log.write(f"\nx  The template specified {complex_type} is not used for systems with more than 1 metal or for organic molecueles.")

        if not template_opt:
            total_data = self.conformer_generation(
                mol,
                name,
                constraints_atoms,
                constraints_dist,
                constraints_angle,
                constraints_dihedral,
                complex_ts,
                charge,
                mult,
                smi,
                geom
            )

        # Updates the dataframe with infromation about conformer generation
        frames = [self.final_dup_data, total_data]
        self.final_dup_data = pd.concat(frames, ignore_index=True, sort=True)

    # automatic detection of metal atoms
    def find_metal_atom(self,mol,charge,mult):
        self.args.metal_atoms = [] # for batch jobs such as CSV inputs with many SMILES
        transition_metals = ['Sc', 'Ti', 'V', 'Cr', 'Mn', 'Fe', 'Co', 'Ni', 'Cu', 'Zn', 'Y', 'Zr', 'Nb', 'Mo',
                            'Tc', 'Ru', 'Rh', 'Pd', 'Ag', 'Cd', 'Hf', 'Ta', 'W', 'Re', 'Os', 'Ir', 'Pt', 'Au',
                            'Hg', 'Rf', 'Db', 'Sg', 'Bh', 'Hs', 'Mt', 'Ds', 'Rg', 'Cn', 'Nh', 'Fl', 'Mc', 'Lv', 'Ts', 'Og']
        for atom in mol.GetAtoms():
            if atom.GetSymbol() in transition_metals:
                self.args.metal_atoms.append(atom.GetSymbol())
        if len(self.args.metal_atoms) > 0:
            self.args.log.write(f"\no  AQME recognized the following metal atoms: {self.args.metal_atoms}")
            if charge is None:
                self.args.log.write(f"\nx  The automated charge calculation might not be precise for metal complexes! You should use the charge option (or the charge column in CSV inputs).")
            if mult is None:
                self.args.log.write(f"\nx  The automated multiplicity calculation might not be precise for metal complexes! You should use the mult option (or the mult column in CSV inputs).")

    def conformer_generation(
        self,
        mol,
        name,
        constraints_atoms,
        constraints_dist,
        constraints_angle,
        constraints_dihedral,
        complex_ts,
        charge,
        mult,
        smi,
        geom,
        coord_Map=None,
        alg_Map=None,
        mol_template=None,
        original_atn=None
    ):
        """
        Function to load mol objects and create 3D conformers

        """

        dup_data = creation_of_dup_csv_csearch(self.args.program.lower())

        dup_data_idx = 0
        status = None

        # Set charge and multiplicity
        # user can overwrite charge and mult with the corresponding arguments
        if charge is None:
            charge = Chem.GetFormalCharge(mol)
        if mult is None:
            mult = Descriptors.NumRadicalElectrons(mol) + 1

        # inputs that go through CREST containing 3D coordinates don't require a previous RDKit conformer sampling
        if (
            self.args.program.lower() in ["crest"]
            and self.args.smi is None
            and os.path.basename(Path(self.args.input)).split(".")[1] in ["pdb","mol2","mol","sdf","gjf","com","xyz"]
        ):

            valid_structure = True
            if self.args.crest_nrun == 1:
                start_time = time.time()
                dup_data.at[dup_data_idx, "Real charge"] = charge
                dup_data.at[dup_data_idx, "Mult"] = mult
                dup_data.at[dup_data_idx, "Molecule"] = name
                status = xtb_opt_main(
                    f"{name}_{self.args.program.lower()}",
                    dup_data,
                    dup_data_idx,
                    self,
                    charge,
                    mult,
                    smi,
                    constraints_atoms,
                    constraints_dist,
                    constraints_angle,
                    constraints_dihedral,
                    'crest',
                    geom,
                    mol=mol,
                )
                n_seconds = round(time.time() - start_time, 2)
                dup_data.at[dup_data_idx, "CSEARCH time (seconds)"] = n_seconds
            else:
                for pt in range(1, int(self.args.crest_nrun)+1):
                    start_time = time.time()
                    dup_data.at[dup_data_idx, "Real charge"] = charge
                    dup_data.at[dup_data_idx, "Mult"] = mult
                    dup_data.at[dup_data_idx, "Molecule"] = name + "_run_{0}".format(pt)
                    shutil.copy(f"{name}_{self.args.program.lower()}.xyz", f"{name}_run_{pt}_{self.args.program.lower()}.xyz")
                    status = xtb_opt_main(
                        f"{name}_run_{pt}_{self.args.program.lower()}",
                        dup_data,
                        dup_data_idx,
                        self,
                        charge,
                        mult,
                        smi,
                        constraints_atoms,
                        constraints_dist,
                        constraints_angle,
                        constraints_dihedral,
                        'crest',
                        geom,
                        mol=mol,
                    )
                    n_seconds = round(time.time() - start_time, 2)
                    dup_data.at[dup_data_idx, "CSEARCH time (seconds)"] = n_seconds
                    dup_data_idx +=1

        else:
            start_time = time.time()
            name = os.path.basename(Path(name)).split(".")[0]
            self.csearch_file = self.csearch_folder.joinpath(
                name + "_" + self.args.program.lower() + self.args.output
            )
            if self.args.crest_nrun != 1 and self.args.program.lower() =='crest':
                sdwriter_init = None
            else:
                sdwriter_init = Chem.SDWriter(str(self.csearch_file))

            valid_structure = filters(
                mol, self.args.log, self.args.max_mol_wt
            )
            if valid_structure:
                try:
                    # the conformational search for RDKit
                    status, dup_data = self.summ_search(
                        mol,
                        name,
                        sdwriter_init,
                        dup_data,
                        dup_data_idx,
                        charge,
                        mult,
                        constraints_atoms,
                        constraints_dist,
                        constraints_angle,
                        constraints_dihedral,
                        complex_ts,
                        coord_Map,
                        alg_Map,
                        mol_template,
                        smi,
                        geom,
                        original_atn
                    )
                except (KeyboardInterrupt, SystemExit):
                    raise

        if status == -1 or not valid_structure:
            error_message = "\nx  ERROR: The structure is not valid or no conformers were obtained from this SMILES string"
            self.args.log.write(error_message)

        #combining all the sdfs from more than one run
        if self.args.crest_nrun != 1:
            sdwriter_rd = Chem.SDWriter(str(self.csearch_file))
            file_runs = glob.glob(str(self.csearch_folder)+'/'+ name +'_run_*'+ self.args.program.lower() +'.sdf')
            allenergy, allmols = [], []
            for file in file_runs:
                mols = Chem.SDMolSupplier(file, removeHs=False)
                for mol in mols:
                    allmols.append(mol)
                    allenergy.append(float(mol.GetProp('Energy')))

            allmols_sorted = [mol for _, mol in sorted(zip(allenergy, allmols), key=lambda pair: pair[0])]
            for mol in allmols_sorted:
                sdwriter_rd.write(mol)
        return dup_data

    def summ_search(
        self,
        mol,
        name,
        sdwriter,
        dup_data,
        dup_data_idx,
        charge,
        mult,
        constraints_atoms,
        constraints_dist,
        constraints_angle,
        constraints_dihedral,
        complex_ts,
        coord_Map,
        alg_Map,
        mol_template,
        smi,
        geom,
        original_atn
    ):

        """
        Embeds, optimizes and filters RDKit conformers
        """

        start_time = time.time()

        # writes sdf for the first RDKit conformer generation
        if not complex_ts:
            if self.args.program.lower() in ['rdkit']:
                self.args.log.write(f"\no  Starting RDKit conformer sampling")
            elif self.args.program.lower() in ['summ','fullmonte']:
                self.args.log.write(f"\no  Starting RDKit-{self.args.program} conformer sampling")
            elif self.args.program.lower() in ['crest'] and os.path.basename(Path(self.args.input)).split(".")[1] not in ["pdb","mol2","mol","sdf","gjf","com","xyz"]:
                self.args.log.write(f"\no  Starting initial RDKit-based mol generation from SMILES")

            status, rotmatches, ff, mol_crest = self.rdkit_to_sdf(
                mol,
                name,
                dup_data,
                dup_data_idx,
                sdwriter,
                charge,
                mult,
                coord_Map,
                alg_Map,
                mol_template,
                smi,
                geom,
                original_atn
            )
            if self.args.program.lower() in ['rdkit','fullmonte'] :
                n_seconds = round(time.time() - start_time, 2)
                dup_data.at[dup_data_idx, "CSEARCH time (seconds)"] = n_seconds

            # this avoids memory issues when using Windows
            try:
                if self.args.crest_nrun != 1 and self.args.program.lower() =='crest':
                    pass
                else:
                    sdwriter.close()
            except RuntimeError:
                pass
            # reads the initial SDF files from RDKit and uses dihedral scan if selected
            if status not in [-1, 0]:
                # getting the energy and mols after rotations
                if self.args.program.lower() == "summ" and len(rotmatches) != 0:
                    status = self.dihedral_filter_and_sdf(
                        name, dup_data, dup_data_idx, coord_Map, alg_Map, mol_template, ff
                    )
                    n_seconds = round(time.time() - start_time, 2)
                    dup_data.at[dup_data_idx, "CSEARCH time (seconds)"] = n_seconds

        if self.args.program.lower() in ['crest']:
            stop_xtb_opt = False
            if not complex_ts:
                # mol_crest is the RDKit-optimized mol object
                if mol_crest is not None:
                    if self.args.crest_nrun == 1:
                        dup_data.at[dup_data_idx, "Molecule"] = name
                        rdmolfiles.MolToXYZFile(mol_crest[0], name + "_crest.xyz")
                    else:
                        # clustering to get the best mol objects
                        cluster_centroird_mols, centroids = cluster_conformers(mol_crest, self.args.heavyonly, self.args.max_matches_rmsd, self.args.crest_nclust)
                        num_start_points = min(int(self.args.crest_nrun), len(cluster_centroird_mols))
                        for pt in range(1, num_start_points+1):
                            dup_data.at[dup_data_idx, "Molecule"] = name + "_run_{0}".format(pt)
                            rdmolfiles.MolToXYZFile(cluster_centroird_mols[pt-1], name + "_run_{0}_crest.xyz".format(pt), confId=centroids[pt-1])

                else:
                    stop_xtb_opt = True
                    status = -1
            else:
                # mol is the raw mol object (no optimization with RDKit to avoid problems when using
                # noncovalent complexes and TSs)
                if mol is not None:
                    if self.args.crest_nrun == 1:
                        dup_data.at[dup_data_idx, "Molecule"] = name
                        rdmolfiles.MolToXYZFile(mol, name + "_crest.xyz")
                    else:
                        num_start_points = min(int(self.args.crest_nrun), len(mol_crest))
                        for pt in range(1, num_start_points+1):
                            dup_data.at[pt-1, "Molecule"] = name + "_run_{0}".format(pt)
                            rdmolfiles.MolToXYZFile(mol, name + "_run_{0}_crest.xyz".format(pt))
                else:
                    stop_xtb_opt = True
                    status = -1
            if not stop_xtb_opt:
                start_time = time.time()
                dup_data.at[dup_data_idx, "Molecule"] = name
                if self.args.crest_nrun == 1:
                    status = xtb_opt_main(
                        f"{name}_{self.args.program.lower()}",
                        dup_data,
                        dup_data_idx,
                        self,
                        charge,
                        mult,
                        smi,
                        constraints_atoms,
                        constraints_dist,
                        constraints_angle,
                        constraints_dihedral,
                        'crest',
                        geom,
                        complex_ts=complex_ts,
                        mol=mol, # this is necessary for CREST calculations with constraints
                        )
                    n_seconds = round(time.time() - start_time, 2)
                    dup_data.at[dup_data_idx, "CSEARCH time (seconds)"] = n_seconds
                else:
                    num_start_points = min(int(self.args.crest_nrun), len(mol_crest))
                    dup_data = pd.DataFrame(np.repeat(dup_data.values, num_start_points, axis=0), columns=dup_data.columns)
                    for pt in range(1, num_start_points+1):
                        start_time = time.time()
                        dup_data.at[pt-1, "Molecule"] = f"{name}_run_{pt}"
                        status = xtb_opt_main(
                            f"{name}_run_{pt}_{self.args.program.lower()}",
                            dup_data,
                            pt-1,
                            self,
                            charge,
                            mult,
                            smi,
                            constraints_atoms,
                            constraints_dist,
                            constraints_angle,
                            constraints_dihedral,
                            'crest',
                            geom,
                            complex_ts=complex_ts,
                            mol=mol, # this is necessary for CREST calculations with constraints

                        )
                        n_seconds = round(time.time() - start_time, 2)
                        dup_data.at[pt-1, "CSEARCH time (seconds)"] = n_seconds

        return status, dup_data

    def dihedral_filter_and_sdf(
        self, name, dup_data, dup_data_idx, coord_Map, alg_Map, mol_template, ff
    ):
        """
        Filtering after dihedral scan to sdf
        """

        rotated_energy = []

        # apply filters
        rdmols = Chem.SDMolSupplier(str(self.csearch_file), removeHs=False)
        if rdmols is None:
            self.args.log.write("\nCould not open " + name + self.args.output)
            if os.path.basename(Path(self.args.input)).split(".")[1] not in ["csv","cdx","txt","yaml","yml","rtf"]:
                self.args.log.finalize()
                sys.exit()
            return

        for i, rd_mol_i in enumerate(rdmols):
            if coord_Map is None and alg_Map is None and mol_template is None:
                energy = minimize_rdkit_energy(
                    rd_mol_i, -1, self.args.log, ff, self.args.opt_steps_rdkit
                )
            else:
                rd_mol_i, energy = realign_mol(
                    rd_mol_i,
                    -1,
                    coord_Map,
                    alg_Map,
                    mol_template,
                    self.args.opt_steps_rdkit,
                )
            rotated_energy.append(energy)

        rotated_cids = list(range(len(rdmols)))
        sorted_rotated_cids = sorted(rotated_cids, key=lambda cid: rotated_energy[cid])

        # filter based on energy window ewin_csearch
        sortedcids_rotated = ewin_filter(
            sorted_rotated_cids,
            rotated_energy,
            dup_data,
            dup_data_idx,
            "summ",
            self.args.ewin_csearch,
        )
        # pre-filter based on energy only
        selectedcids_initial_rotated = pre_E_filter(
            sortedcids_rotated,
            rotated_energy,
            dup_data,
            dup_data_idx,
            "summ",
            self.args.initial_energy_threshold,
        )
        # filter based on energy and RMSD
        selectedcids_rotated = RMSD_and_E_filter(
            rdmols,
            selectedcids_initial_rotated,
            rotated_energy,
            self.args,
            dup_data,
            dup_data_idx,
            "summ",
        )
        mol_select = []
        for i, cid in enumerate(selectedcids_rotated):
            mol_rd = Chem.RWMol(rdmols[cid])
            mol_rd.SetProp("_Name", rdmols[cid].GetProp("_Name") + " " + str(i))
            mol_rd.SetProp("Energy", str(rotated_energy[cid]))
            if len(self.args.metal_atoms) >= 1:
                set_metal_atomic_number(
                    mol_rd, self.args.metal_idx, self.args.metal_sym
                )
            mol_select.append(mol_rd)

        # update SDF file
        os.remove(self.csearch_file)
        sdwriter_rd = Chem.SDWriter(str(self.csearch_file))
        for mol in mol_select:
            sdwriter_rd.write(mol)
        sdwriter_rd.close()
        status = 1
        return status

    def auto_sampling(self, mol):
        """
        Detects automatically the initial number of conformers for the sampling
        """

        if len(self.args.metal_atoms) >= 1:
            if len(self.args.metal_idx) > 0:
                self.args.auto_sample = (
                    self.args.auto_sample * 3 * len(self.args.metal_idx)
                )  # this accounts for possible trans/cis isomers in metal complexes
        auto_samples = 0
        auto_samples += 3 * (Lipinski.NumRotatableBonds(mol))  # x3, for C3 rotations
        auto_samples += 3 * (Lipinski.NHOHCount(mol))  # x3, for OH/NH rotations
        auto_samples += 3 * (
            Lipinski.NumSaturatedRings(mol)
        )  # x3, for boat/chair/envelope confs
        if auto_samples == 0:
            auto_samples = self.args.auto_sample
        else:
            auto_samples = self.args.auto_sample * auto_samples
        return auto_samples

    def genConformer_r(
        self,
        mol,
        conf,
        i,
        matches,
        sdwriter,
        name,
        update_to_rdkit,
        coord_Map,
        alg_Map,
        mol_template,
        original_atn,
        geom
    ):
        """
        If program = RDKit, this replaces iodine back to the metal (if needed)
        and writes the RDKit SDF files. With program = summ, this function
        optimizes rotamers
        """

        if i >= len(matches):  # base case, torsions should be set in conf
            if len(self.args.metal_atoms) >= 1 and (
                self.args.program.lower() in ["rdkit","crest"] or update_to_rdkit
            ):
                if coord_Map is None and alg_Map is None and mol_template is None:
                    energy = minimize_rdkit_energy(
                        mol,
                        conf,
                        self.args.log,
                        self.args.ff,
                        self.args.opt_steps_rdkit,
                    )
                else:
                    mol, energy = realign_mol(
                        mol,
                        conf,
                        coord_Map,
                        alg_Map,
                        mol_template,
                        self.args.opt_steps_rdkit,
                    )
                mol.SetProp("Energy", str(energy))

                # setting the metal back instead of I
                set_metal_atomic_number(mol, self.args.metal_idx, self.args.metal_sym)

                # setting the problematic As atoms back when using the Ir_squareplanar geometry rule
                if geom == ['Ir_squareplanar']:
                    if original_atn is not None:
                        mol.GetAtomWithIdx(original_atn[1]).SetAtomicNum(original_atn[0])

            # if CREST is used, this RDKit preoptimzed mol object will be employed to initializethe the trajectories
            if self.args.program.lower() in ["crest"]:
                return mol
            else:
                try:
                    sdwriter.write(mol, conf)
                except (TypeError):
                    raise
                return 1

        elif self.args.program.lower() in ["crest"]:
            # setting the metal back instead of I
            set_metal_atomic_number(mol, self.args.metal_idx, self.args.metal_sym)

            return mol

        # when SUMM is selected, this cycle generates conformers based on rotation of dihedral angles
        total = 0
        deg = 0
        while deg < 360.0:
            rad = math.pi * deg / 180.0
            rdMolTransforms.SetDihedralRad(
                mol.GetConformer(conf), *matches[i], value=rad
            )
            mol.SetProp("_Name", name)
            total += self.genConformer_r(
                mol,
                conf,
                i + 1,
                matches,
                sdwriter,
                name,
                update_to_rdkit,
                coord_Map,
                alg_Map,
                mol_template,
                original_atn,
                geom
            )
            deg += int(self.args.degree)

        return total

    def embed_conf(self, mol, initial_confs, coord_Map, alg_Map, mol_template):
        """
        Function to embed conformers
        """

        is_sdf_mol_or_mol2 = os.path.basename(Path(self.args.input)).split('.')[1].lower() in [
            "sdf",
            "mol",
            "mol2",
        ]

        if is_sdf_mol_or_mol2:
            Chem.AssignStereochemistryFrom3D(mol)

        embed_kwargs = dict()
        embed_kwargs["ignoreSmoothingFailures"] = True
        embed_kwargs["randomSeed"] = self.args.seed
        embed_kwargs["numThreads"] = 0

        if (coord_Map, alg_Map, mol_template) != (None, None, None):
            embed_kwargs["coordMap"] = coord_Map
        cids = rdDistGeom.EmbedMultipleConfs(mol, initial_confs, **embed_kwargs)

        if len(cids) <= 1 and initial_confs != 1:
            self.args.log.write(f"\nx  Normal RDKit embeding process failed, trying to generate conformers with random coordinates (with {str(initial_confs)} possibilities)")
            embed_kwargs["useRandomCoords"] = True
            embed_kwargs["boxSizeMult"] = 10.0
            embed_kwargs["numZeroFail"] = 1000
            embed_kwargs["numThreads"] = 0
            cids = rdDistGeom.EmbedMultipleConfs(mol, initial_confs, **embed_kwargs)

        if is_sdf_mol_or_mol2:
            # preserving AssignStereochemistryFrom3D
            for cid in cids:
                Chem.AssignAtomChiralTagsFromStructure(mol, confId=cid)

        return cids

    def min_and_E_calc(self, mol, cids, coord_Map, alg_Map, mol_template, ff, geom):
        """
        Minimization and E calculation with RDKit after embeding
        """

        cenergy, outmols = [], []

        for _, conf in enumerate(cids):
            if coord_Map is None and alg_Map is None and mol_template is None:
                energy = minimize_rdkit_energy(
                    mol, conf, self.args.log, ff, self.args.opt_steps_rdkit
                )
            else:  # template realign before doing calculations
                mol, energy = realign_mol(
                    mol,
                    conf,
                    coord_Map,
                    alg_Map,
                    mol_template,
                    self.args.opt_steps_rdkit,
                )

            # removes geometries that do not pass the filters (geom option)
            mol_geom = Chem.Mol(mol)
            if len(self.args.metal_atoms) >= 1:
                set_metal_atomic_number(mol_geom, self.args.metal_idx, self.args.metal_sym)
            passing_geom = geom_filter(self,mol_geom,geom)
            if passing_geom:
                cenergy.append(energy)
                pmol = PropertyMol.PropertyMol(mol)
                outmols.append(pmol)

        return outmols, cenergy

    def min_after_embed(
        self,
        mol,
        cids,
        name,
        rotmatches,
        dup_data,
        dup_data_idx,
        sdwriter,
        update_to_rdkit,
        coord_Map,
        alg_Map,
        mol_template,
        charge,
        mult,
        ff,
        smi,
        geom,
        original_atn
    ):
        """
        Minimizes, gets the energy and filters RDKit conformers after embeding
        """

        # gets optimized mol objects and energies
        if geom != []:
            self.args.log.write(f"o  Applying geometry filters ({geom})")
        outmols, cenergy = self.min_and_E_calc(
            mol, cids, coord_Map, alg_Map, mol_template, ff, geom
        )

        # writing charges and multiplicity after RDKit
        dup_data.at[dup_data_idx, "Mult"] = mult
        dup_data.at[dup_data_idx, "Real charge"] = charge

        for i, cid in enumerate(cids):
            outmols[cid].SetProp("_Name", name + " " + str(i + 1))
            outmols[cid].SetProp("Energy", str(cenergy[cid]))
            outmols[cid].SetProp("Real charge", str(charge))
            outmols[cid].SetProp("Mult", str(mult))
            outmols[cid].SetProp("SMILES", str(smi))

        # sorts the energies
        cids = list(range(len(outmols)))
        sorted_all_cids = sorted(cids, key=lambda cid: cenergy[cid])

        self.args.log.write("\no  Applying filters to initial conformers")

        # filter based on energy window ewin_csearch
        sortedcids_rdkit = ewin_filter(
            sorted_all_cids,
            cenergy,
            dup_data,
            dup_data_idx,
            "rdkit",
            self.args.ewin_csearch,
        )

        # pre-filter based on energy only
        selectedcids_initial_rdkit = pre_E_filter(
            sortedcids_rdkit,
            cenergy,
            dup_data,
            dup_data_idx,
            "rdkit",
            self.args.initial_energy_threshold,
        )

        # filter based on energy and RMSD
        selectedcids_rdkit = RMSD_and_E_filter(
            outmols,
            selectedcids_initial_rdkit,
            cenergy,
            self.args,
            dup_data,
            dup_data_idx,
            "rdkit",
        )

        if self.args.program.lower() in ["summ", "rdkit", "crest"]:
            # now exhaustively drive torsions of selected conformers
            total = 0
            for conf in selectedcids_rdkit:
                if self.args.program.lower() == "summ" and not update_to_rdkit:
                    sdwriter.write(outmols[conf], conf)
                    for m in rotmatches:
                        rdMolTransforms.SetDihedralDeg(
                            outmols[conf].GetConformer(conf), *m, 180.0
                        )
                if self.args.program.lower() in ["summ", "rdkit"]:
                    total += self.genConformer_r(
                        outmols[conf],
                        conf,
                        0,
                        rotmatches,
                        sdwriter,
                        outmols[conf].GetProp("_Name"),
                        update_to_rdkit,
                        coord_Map,
                        alg_Map,
                        mol_template,
                        original_atn,
                        geom
                    )
                elif self.args.program.lower() in ["crest"]:
                    mol = self.genConformer_r(
                        outmols[conf],
                        conf,
                        0,
                        rotmatches,
                        sdwriter,
                        outmols[conf].GetProp("_Name"),
                        update_to_rdkit,
                        coord_Map,
                        alg_Map,
                        mol_template,
                        original_atn,
                        geom
                    )
                    outmols = [mol]
                    break

            status = 1

        if self.args.program.lower() == "summ":
            dup_data.at[dup_data_idx, "summ-conformers"] = total

        if self.args.program.lower() == "fullmonte":
            status = generating_conformations_fullmonte(
                name,
                self.args,
                rotmatches,
                selectedcids_rdkit,
                outmols,
                sdwriter,
                dup_data,
                dup_data_idx,
                coord_Map,
                alg_Map,
                mol_template,
                ff,
            )
            # removes the rdkit file
            os.remove(name + "_" + "rdkit" + self.args.output)

        return status, outmols

    def rdkit_to_sdf(
        self,
        mol,
        name,
        dup_data,
        dup_data_idx,
        sdwriter,
        charge,
        mult,
        coord_Map,
        alg_Map,
        mol_template,
        smi,
        geom,
        original_atn
    ):

        """
        Conversion from RDKit to SDF
        """

        try:
            Chem.SanitizeMol(mol)
            mol = Chem.AddHs(mol)
        except Chem.AtomValenceException: # this happens sometimes with complex metals when substituting the metal with an I atom
            self.args.log.write(f'\nx  The species provided could not be converted into a mol object wth RDKit. It normally happens with tricky metal complexes and might be fixed with a couple tricks (i.e., changing a single bond + positive charge with a double bond).')
            return -1, None, None, None

        mol.SetProp("_Name", name)

        # detects and applies auto-detection of initial number of conformers
        if self.args.program.lower() in ['crest']:
            # CREST only uses the most stable conformer from RDKit,
            # and initial_confs can be low to speed up the process
            initial_confs = self.args.auto_sample
        elif self.args.sample == "auto":
            initial_confs = int(self.auto_sampling(mol))
        else:
            initial_confs = int(self.args.sample)

        update_to_rdkit = False

        rotmatches = getDihedralMatches(mol, self.args.heavyonly)

        if len(rotmatches) > self.args.max_torsions and self.args.max_torsions > 0:
            self.args.log.write(f"\nx  Too many torsions ({len(rotmatches)}). Skipping {name + self.args.output}")
        elif self.args.program.lower() == "summ" and len(rotmatches) == 0:
            update_to_rdkit = True
            self.args.log.write("\nx  No rotatable dihedral found. Updating to CSEARCH to RDKit, writing to SUMM SDF")
        elif self.args.program.lower() == "fullmonte" and len(rotmatches) == 0:
            update_to_rdkit = True
            self.args.log.write("\nx  No rotatable dihedral found. Updating to CSEARCH to RDKit, writing to FULLMONTE SDF")

        ff = self.args.ff
        dup_data.at[dup_data_idx, "RDKit-Initial-samples"] = initial_confs
        if self.args.program.lower() == "rdkit":
            rotmatches = []
        cids = self.embed_conf(mol, initial_confs, coord_Map, alg_Map, mol_template)

        # energy minimize all to get more realistic results
        # identify the atoms and decide Force Field
        for atom in mol.GetAtoms():
            if atom.GetAtomicNum() > 36 and self.args.ff == "MMFF":  # up to Kr for MMFF, if not the code will use UFF
                self.args.log.write(f"\nx  {self.args.ff} is not compatible with the molecule, changing to UFF")
                ff = "UFF"

        try:
            status, mol_crest = self.min_after_embed(
                mol,
                cids,
                name,
                rotmatches,
                dup_data,
                dup_data_idx,
                sdwriter,
                update_to_rdkit,
                coord_Map,
                alg_Map,
                mol_template,
                charge,
                mult,
                ff,
                smi,
                geom,
                original_atn
            )
        except IndexError:
            status = -1
            mol_crest = None

        if self.args.crest_nrun != 1 and self.args.program.lower() =='crest':
            pass
        else:
            sdwriter.close()

        return status, rotmatches, ff, mol_crest


#!/usr/bin/env python
# -*- coding: utf-8 -*-
#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/>
#
# Author: Francois Boulogne <fboulogne at sciunto dot org>, 2012

import unittest
import subprocess

class TestCitebib(unittest.TestCase):

    ###########
    # BAD USAGE
    ###########
    @unittest.skip('finish this test')
    def test_not_existing_bibfile(self):
        command = ['citebib', '-b', 'tests/data/not_existing.bib', '-t', 'tests/data/doc_article.tex']
        process = subprocess.Popen(command, bufsize=4096, stdout=subprocess.PIPE)
        stdout, stderr = process.communicate()
        out = stdout.decode()

    @unittest.skip('finish this test')
    def test_not_existing_texfile(self):
        command = ['citebib', '-b', 'tests/data/article.bib', '-t', 'tests/data/not_existing.tex']
        process = subprocess.Popen(command, bufsize=4096, stdout=subprocess.PIPE)
        stdout, stderr = process.communicate()
        out = stdout.decode()

    @unittest.skip('finish this test')
    def test_not_missingkey_in_bibtex(self):
        command = ['citebib', '-b', 'tests/data/book.bib', '-t', 'tests/data/doc_article.tex']
        process = subprocess.Popen(command, bufsize=4096, stdout=subprocess.PIPE)
        stdout, stderr = process.communicate()
        out = stdout.decode()

    ###########
    # ARTICLES
    ###########
    def test_article_default(self):
        command = ['citebib', '-b', 'tests/data/article.bib', '-t', 'tests/data/doc_article.tex']
        process = subprocess.Popen(command, bufsize=4096, stdout=subprocess.PIPE)
        stdout, stderr = process.communicate()
        out = stdout.decode()
        expected = """@article{Cesar2013,
\tauthor = {C{\\'e}sar, J.},
\tjournal = {Nice Journal},
\tpages = {12--23},
\ttitle = {{A}n amazing title},
\tvolume = {12},
\tyear = {2013},
}\n
"""
        self.assertEqual(out, expected)
        self.assertEqual(stderr, None)

    ###########
    # BOOKS
    ###########
    def test_book_default(self):
        command = ['citebib', '-b', 'tests/data/book.bib', '-t', 'tests/data/doc_book.tex']
        process = subprocess.Popen(command, bufsize=4096, stdout=subprocess.PIPE)
        stdout, stderr = process.communicate()
        out = stdout.decode()
        expected = """@book{Bird1987,
\tauthor = {Bird, R.B. and Armstrong, R.C. and Hassager, O.},
\tpublisher = {Wiley Edition},
\ttitle = {{D}ynamics of {P}olymeric {L}iquid},
\tyear = {1987},
}\n
"""
        self.assertEqual(out, expected)
        self.assertEqual(stderr, None)


import argparse
import os
import re
import sys
import time
import common.python.cls_cmd as cls_cmd

def main() -> None:
    args = _parse_args()
    status = generic_deployment(args.tag)
    sys.exit(status)

def _parse_args() -> argparse.Namespace:
    parser = argparse.ArgumentParser(description='Sanity Check')

    parser.add_argument(
        '--tag', '-t',
        action='store',
        required=True,
        help='Image Tag in image-name:image tag format',
    )
    return parser.parse_args()

def generic_deployment(tag):
    # First detect which docker/podman command to use
    cli = ''
    myCmds = cls_cmd.LocalCmd()
    cmd = 'which podman || true'
    podman_check = myCmds.run(cmd, silent=True)
    if re.search('podman', podman_check.stdout):
        cli = 'sudo podman'
    if cli == '':
        cmd = 'which docker || true'
        docker_check = myCmds.run(cmd, silent=True)
        if re.search('docker', docker_check.stdout):
            cli = 'docker'
    if cli == '':
        print ('No docker / podman installed: quitting')
        return -1

    status = 0
    if cli == 'docker':
        infos = tag.split(':')
        cmd = f'sed -e "s@NEF_IMAGE_TAG@{infos[1]}@" ci-scripts/docker-compose/docker-compose.tplt > ci-scripts/docker-compose/docker-compose.yaml'
        myCmds.run(cmd)
        cmd = 'cd ci-scripts/docker-compose && docker-compose up -d'
        upStatus = myCmds.run(cmd)
        for line in upStatus.stdout.split('\n'):
            print(line)
        time.sleep(20)
        cmd = 'docker inspect --format="STATUS: {{.State.Health.Status}}" cicd-oai-nef'
        healthStatus = myCmds.run(cmd)
        for line in healthStatus.stdout.split('\n'):
            print(line)
            if re.search('STATUS:', line):
                if re.search('STATUS: healthy', line):
                    status = 0
                else:
                    status = -1
        cmd = 'docker logs cicd-oai-nef'
        logStatus = myCmds.run(cmd)
        for line in logStatus.stdout.split('\n'):
            print(line)
        time.sleep(5)
        cmd = 'cd ci-scripts/docker-compose && docker-compose down'
        downStatus = myCmds.run(cmd)
        for line in downStatus.stdout.split('\n'):
            print(line)
        cmd = 'docker volume prune --force || true'
        myCmds.run(cmd)
    else:
        cmd = 'sudo podman network create --subnet 192.168.28.192/26 --ip-range 192.168.28.192/26 cicd-oai-public-net'
        netUpStatus = myCmds.run(cmd)
        for line in netUpStatus.stdout.split('\n'):
            print(line)
        time.sleep(1)
        cwd = os.getcwd()
        cmd = 'sudo podman run --name cicd-oai-nef --network cicd-oai-public-net --ip 192.168.28.210'
        cmd += f' --env-file ./ci-scripts/podman/nef.env -d {tag}'
        contUpStatus = myCmds.run(cmd)
        for line in contUpStatus.stdout.split('\n'):
            print(line)
        time.sleep(20)
        cmd = 'sudo podman inspect --format="STATUS: {{.State.Healthcheck.Status}}" cicd-oai-nef'
        healthStatus = myCmds.run(cmd)
        for line in healthStatus.stdout.split('\n'):
            print(line)
            if re.search('STATUS:', line):
                if re.search('STATUS: healthy', line):
                    status = 0
                else:
                    status = -1
        cmd = 'sudo podman logs cicd-oai-nef'
        logStatus = myCmds.run(cmd)
        for line in logStatus.stdout.split('\n'):
            print(line)
        cmd = 'sudo podman rm -f cicd-oai-nef'
        contDwnStatus = myCmds.run(cmd)
        for line in contDwnStatus.stdout.split('\n'):
            print(line)
        cmd = 'sudo podman network rm cicd-oai-public-net'
        netDwnStatus = myCmds.run(cmd)
        for line in netDwnStatus.stdout.split('\n'):
            print(line)
        cmd = 'sudo podman volume prune --force || true'
        myCmds.run(cmd)
    return status

if __name__ == '__main__':
    main()


'''File containing the functions for the server'''
class Seq:
    def __init__(self, strbases):
        self.strbases = strbases

    def len(self):
        return len(self.strbases)

    def count(self,base):
        count_a = count_c = count_g = count_t = 0
        for b in base:
            if b == 'A':
                count_a += 1
            elif b == 'C':
                count_c += 1
            elif b == 'G':
                count_g += 1
            elif b == 'T':
                count_t += 1
        dictionary = {'A': count_a, 'C': count_c, 'G': count_g, 'T': count_t}
        return dictionary

    def perc(self,base, seq):
        counter = self.count(seq)
        counter = counter.get(base)
        percentage = round(100.0 * counter / self.len(), 1)
        return percentage


import winname
import time
from collections import OrderedDict

substitution = {'Key.enter':'[ENTER]', 'Key.backspace':'[BACKSPACE]', 'Key.space': ' ',
	'Key.alt_l':'[ALT]', 'Key.tab':'[TAB]', 'Key.delete':'[DEL]', 'Key.ctrl_l':'[CTRL]',
	'Key.left':'[LEFT ARROW]','Key.right':'[RIGHT ARROW]','Key.shift':'[SHIFT]','\\x13':
	'[CTRL-S]','\\x17':'[CTRL-W]', 'Key.caps_lock':'[CAPS LK]', '\\x01':'[CTRL-A]', 'Key.cmd':
	'[WINDOWS KEY]', 'Key.print_screen':'[PRNT SCR]', '\\x03':'[CTRL-C]', '\\x16':'[CTRL-V]'}

cache = OrderedDict()
chain = ''

def on_press(key):
	k = str(key)
	global cache
	global chain
	win = winname.active_window()
	cache['stamp'] = time.ctime()
	if len(cache)==1:
		cache['win'] = win
		if k in substitution.keys():
			s =substitution[k]
			chain +=s
		else:
			s = k.replace("'","")
			chain +=s

	else:
		if cache['win'] == win:
			if k in substitution.keys():
				s =substitution[k]
				chain +=s
			else:
				s = k.replace("'","")
				chain +=s

		else:
			cache['keys'] = chain
			chain = ''
			od =OrderedDict(cache)
			for key in cache.keys():
				cache[key] = ''
			#cache = OrderedDict()
			cache['win'] = win
			if k in substitution.keys():
				s =substitution[k]
				chain +=s

			else:
				s = k.replace("'","")
				chain +=s
			s = f"stamp: {od['stamp']}	window: {od['win']} keys: {od['keys']}\n"
			with open('output/log_.txt', 'a+') as f:
				f.writelines(s)


import torch
import numpy as np

from swaggy.regression_model import Resnet18Regression

def track_SWAG_params(model,thetaSWA,theta2,SWAGpoch):
    # Don't track any changes to the weights
    with torch.no_grad():
        nn = []
        for param in model.parameters():
            # Convert weights of each layer to numpy array
            param_numpy = param.detach().numpy()
            nn.append(param_numpy)
        # Convert python list of numpy arrays to numpy array
        nn = np.array(nn,dtype=object)

        if SWAGpoch == 0:
          # Update our running sum
          thetaSWA = nn
          # placeholder for theta2
          theta2 = 0 * nn
        else:
          # Calculating theta_SWA: thetaSWA = 1/T Sum_i theta_i
          thetaSWA = 1./(SWAGpoch + 1) * ((SWAGpoch)*thetaSWA + nn)
          # Calculating bar{theta}^2: \bar{\theta}^2 = 1/T Sum_i theta_i^2
          theta2 = 1./(SWAGpoch + 1) * (np.power(nn,2) + (SWAGpoch)*theta2)

    return thetaSWA, theta2

def sample_model(thetaSWA,theta2):
  model_swag = Resnet18Regression(1,1).to("cpu")

  # Loop trough copy of model, grab new weights stochastically
  with torch.no_grad():
    for param, tSWA, t2 in zip(model_swag.parameters(), thetaSWA, theta2):
      mean, sigma = tSWA, np.sqrt(t2)
      param.data = torch.from_numpy(np.random.normal(mean,sigma))

  return model_swag


# -*- coding: utf-8 -*-

from __future__ import unicode_literals, print_function

__author__ = 'ufian'

import datetime

from telepot.namedtuple import InlineQueryResultArticle, InputTextMessageContent, \
    InlineKeyboardMarkup, InlineKeyboardButton, \
    ReplyKeyboardMarkup, KeyboardButton, ReplyKeyboardRemove

import utils as u
import calculator as calc
import dialog as d

DEFAULT = 'default'
EAT = 'eat'
PRODUCT = 'product'
TODAY = 'today'
STAT = 'stat'

class StageException(Exception):
    pass

class SessionMixin(object):
    def __init__(self, session):
        self.session = session

    @property
    def user_id(self):
        return self.session.user_id

    @property
    def context(self):
        return self.session.context

    @property
    def user_data(self):
        return self.session.user_data

    def set_stage(self, stage):
        return self.session.stage(stage)

    def sendMessage(self, *args, **kwargs):
        res = self.session.sender.sendMessage(*args, **kwargs)

        if 'last_message' in self.user_data:
            try:
                self.session.bot.editMessageReplyMarkup(tuple(self.user_data['last_message']), reply_markup=None)
            except:
                pass
            del self.user_data['last_message']

        if 'last_message' in self.context:
            self.session.bot.editMessageReplyMarkup(self.context['last_message'], reply_markup=None)

        self.context['last_message'] = u.get_edit_id(res)

        return res

    def editMessageText(self, *args, **kwargs):
        return self.session.bot.editMessageText(*args, **kwargs)

    def editCBMessageText(self, *args, **kwargs):
        if 'cb_message' not in self.context:
            raise StageException('Not found callback action')
        return self.editMessageText(self.context['cb_message'], *args, **kwargs)

class BaseStage(SessionMixin):
    STAGE = None

    def _today_text(self):
        return u"Сегодня съедено {0} ккал".format(
            calc.get_today_calories(self.user_id)
        )

    def _last_update(self):
        udt = datetime.datetime.utcnow()
        return u"Обновлено: {time} {date}".format(
            date=u.get_date(udt),
            time=u.get_time(udt)
        )

    def _get_path(self, path):
        if isinstance(path, basestring):
            stage = self.STAGE
        elif isinstance(path, tuple):
            stage, path = path
        else:
            raise StageException('Path is wrong `{0}`'.format(path))

        return '{0}|{1}'.format(stage, path)

    def get_keyboard(self, rows):
        keys = list()
        for row in rows:
            key_row = list()
            for name, path in row:
                key_row.append(InlineKeyboardButton(text=name, callback_data=self._get_path(path)))
            keys.append(key_row)

        return InlineKeyboardMarkup(
            inline_keyboard=keys
        )

    def base_keyboard(self):
        return self.get_keyboard([
            [
                ('Поесть', (EAT, u'main')),
                ('Продукты', (PRODUCT, u'main')),
            ],
            [
                ('Сегодня', (TODAY, u'main')),
                ('Статистика', (STAT, u'main')),
            ]
        ])

    def base_message(self, message=None, update_msg=None, with_keyboard=True):
        parts = list()
        if message:
            parts.extend([message, u''])

        parts.append(self._today_text())
        parts.append(self._last_update())

        msg = u"\n".join(parts)

        if with_keyboard:
            keyboard = self.base_keyboard()
        else:
            keyboard = None

        if update_msg:
            try:
                return self.editMessageText(update_msg, msg, reply_markup=keyboard)
            except:
                pass

        return self.sendMessage(msg, reply_markup=keyboard)

    def on_chat_message(self, msg, text):
        pass

    def on_callback_query(self, msg, cb_data):
        pass

class DefaultStage(BaseStage):
    STAGE = DEFAULT

    def on_chat_message(self, msg, text):
        sep = u' по '
        result = u'Ошибка'

        if sep in text:
            weight, calories = text.split(sep, 1)
            try:
                weight = int(weight)
                calories = int(calories)

                calc.add_eating(
                    user_id=self.user_id,
                    product_id=None,
                    calories=calories,
                    weight=weight
                )

                result = u'Сохранено'
            except ValueError:
                result = u'Не сохранено. Проблема с целыми числами.'
            except:
                pass
        else:
            try:
                calories = int(text)
                calc.add_eating(
                    user_id=self.user_id,
                    product_id=None,
                    calories=calories,
                    weight=None
                )
                result = u'Сохранено'
            except ValueError:
                result = u'Не сохранено. Проблема с целыми числами.'
            except:
                pass

        res = self.base_message(result)

        # {u'date': 1498604286,
        #  u'text': u'\u0421\u0435\u0433\u043e\u0434\u043d\u044f \u0441\u044a\u0435\u0434\u0435\u043d\u043e 0 \u043a\u043a\u0430\u043b\n\u041e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u043e: 15:59 27.06.2017',
        #  u'from': {u'username': u'hsetestbot', u'first_name': u'nonamebot', u'id': 161113648},
        #  u'message_id': 114,
        #  u'chat': {u'username': u'Ufian', u'first_name': u'Mikhail', u'last_name': u'Ufian', u'type': u'private', u'id': 113239144}}

    def on_callback_query(self, msg, cb_data):
        self.base_message(update_msg=self.context['cb_message'])

class ProductStage(BaseStage):
    STAGE = PRODUCT

    PARAMS = [
        d.DialogParam('name', 'название',
                      question='Введите название продукта'),
        d.DialogParam('calories', 'калории', validate='int',
                      question='Введите калорийность продукта'),
    ]

    def _get_dialog(self, state):
        return d.Dialog(self.PARAMS, state)

    def on_chat_message(self, msg, text):
        dialog = self._get_dialog(self.context['product'])
        res = dialog.set_answer(text)

        if res in (d.Dialog.CONTINUE, d.Dialog.BAD_VALUE):
            self.sendMessage(dialog.get_question())
            return

        if res == d.Dialog.FINISH:
            calc.add_product(self.user_id, **self.context['product'])

            self.set_stage(DEFAULT).base_message('Сохранено')

    def on_callback_query(self, msg, cb_data):
        if cb_data == u'main':
            parts = list()

            for row in calc.get_products(self.user_id):
                parts.append(u"{0}: {1} ккал".format(row.name, row.calories))

            text = u"\n".join(parts)

            self.editCBMessageText(text, reply_markup=self.get_keyboard([
                [
                    (u'Добавить', u'add'),
                    (u'Отмена', (DEFAULT, u'main'))
                ]
            ]))
        elif cb_data == u'add':
            self.context['product'] = {}
            dialog = self._get_dialog(self.context['product'])
            self.sendMessage(dialog.get_question())

class EatStage(BaseStage):
    STAGE = EAT

    def on_callback_query(self, msg, cb_data):
        self.set_stage(DEFAULT).base_message('Обеды не найдены')

class TodayStage(BaseStage):
    STAGE = TODAY

    def _today_stat(self):
        parts = list()
        for row in calc.get_today_list(self.user_id):
            line = u""

            if row.date is not None:
                line += u"{} ".format(u.get_time(row.date))

            if row.calories is not None:
                if row.weight is not None:
                    line += u"{0} ккал ({1} г, {2} ккал/100г)".format(int(row.calories * row.weight / 100), row.weight, row.calories)
                else:
                    line += u"{0} ккал".format(row.calories)

            if len(line) > 0:
                parts.append(line)

        return u"\n".join(parts)

    def on_callback_query(self, msg, cb_data):
        text = self._today_stat()

        self.editCBMessageText(text, reply_markup=self.get_keyboard([
            [
                (u'Отмена', (DEFAULT, u'main'))
            ]
        ]))

class StatStage(BaseStage):
    STAGE = STAT

    def _get_stat(self):
        parts = list()

        for row in calc.get_stat_data(self.user_id, days=31):
            date = row.get('date')
            calories = row.get('calories')

            line = u""

            if date is not None:
                line += u"{} ".format(u.get_date(date))

            if calories is not None:
                    line += u"{0} ккал".format(calories)

            if len(line) > 0:
                parts.append(line)

        return u"\n".join(parts)

    def on_callback_query(self, msg, cb_data):
        text = self._get_stat()

        self.editCBMessageText(text, reply_markup=self.get_keyboard([
            [
                (u'Отмена', (DEFAULT, u'main'))
            ]
        ]))


from audioop import reverse
from multiprocessing import context
from django.shortcuts import render
from django.http import HttpResponse, HttpResponseRedirect
from listings.models import Band
from listings.form_contact import ContactUsForm ,BandForm
from django.shortcuts import redirect
from django.core.mail import send_mail

# ajoutez cet import
# Create your views here.

def band_list(request):
    bands  = Band.objects.all()
    return render (request,'listings/band_list.html',context ={"bands":bands})

def band_detail(request, id):
    band = Band.objects.get(id=id)
    return render(request,
                    'listings/band_detail.html',
                    {'band':band})

def about(request):
    return render(request,'listings/about.html')
def contact(request):
    if request.method == 'POST':
        form = ContactUsForm(request.POST)
        if form.is_valid():
           send_mail(
             subject=f'Message from {form.cleaned_data["nom"] or "anonyme"} via MerchEx Contact Us form',
             message=form.cleaned_data['message'],
                 from_email=form.cleaned_data['email'],
             recipient_list=['admin@merchex.xyz'],
        )

        return redirect("email-envoyer")

    else:
        form = ContactUsForm()

    return render(request,
                  'listings/contact.html',
                  {'form':form})
def email_send(request):
    return render(request,"listings/email_send.html")

def band_create(request):
    if request.method == "POST":
        form = BandForm(request.POST)
        if form.is_valid():
            band = form.save()
            return redirect('band-detail', band.id)
    else: form =BandForm()
    return render(request,'listings/band_create.html',{'form':form})


#!/usr/bin/env python

import sys, fnmatch
import importlib

# peripheral types that can be shared, wildcard patterns
SHARED_MAP = ["I2C*", "USART*_TX", "UART*_TX", "SPI*", "TIM*_UP"]

ignore_list = []
dma_map = None

debug = False

def check_possibility(periph, dma_stream, curr_dict, dma_map, check_list):
    for other_periph in curr_dict:
        if other_periph != periph:
            if curr_dict[other_periph] == dma_stream:
                ignore_list.append(periph)
                check_str = "%s(%d,%d) %s(%d,%d)" % (
                    other_periph, curr_dict[other_periph][0],
                    curr_dict[other_periph][1], periph, dma_stream[0],
                    dma_stream[1])
                #check if we did this before
                if check_str in check_list:
                    return False
                check_list.append(check_str)
                if debug:
                    print("Trying to Resolve Conflict: ", check_str)
                #check if we can resolve by swapping with other periphs
                for streamchan in dma_map[other_periph]:
                    stream = (streamchan[0], streamchan[1])
                    if stream != curr_dict[other_periph] and \
                       check_possibility(other_periph, stream, curr_dict, dma_map, check_list):
                        curr_dict[other_periph] = stream
                        return True
                return False
    return True

def can_share(periph, noshare_list):
    '''check if a peripheral is in the SHARED_MAP list'''
    for noshare in noshare_list:
        if fnmatch.fnmatch(periph, noshare):
            return False
    for f in SHARED_MAP:
        if fnmatch.fnmatch(periph, f):
            return True
    if debug:
        print("%s can't share" % periph)
    return False

def chibios_dma_define_name(key):
    '''return define name needed for board.h for ChibiOS'''
    if key.startswith('ADC'):
        return 'STM32_ADC_%s_DMA_' % key
    elif key.startswith('SPI'):
        return 'STM32_SPI_%s_DMA_' % key
    elif key.startswith('I2C'):
        return 'STM32_I2C_%s_DMA_' % key
    elif key.startswith('USART'):
        return 'STM32_UART_%s_DMA_' % key
    elif key.startswith('UART'):
        return 'STM32_UART_%s_DMA_' % key
    elif key.startswith('SDIO') or key.startswith('SDMMC'):
        return 'STM32_SDC_%s_DMA_' % key
    elif key.startswith('TIM'):
        return 'STM32_TIM_%s_DMA_' % key
    else:
        print("Error: Unknown key type %s" % key)
        sys.exit(1)

def get_list_index(peripheral, priority_list):
    '''return index into priority_list for a peripheral'''
    for i in range(len(priority_list)):
        str = priority_list[i]
        if fnmatch.fnmatch(peripheral, str):
            return i
    # default to max priority
    return len(priority_list)

def get_sharing_priority(periph_list, priority_list):
    '''get priority of a list of peripherals we could share with'''
    highest = len(priority_list)
    for p in periph_list:
        prio = get_list_index(p, priority_list)
        if prio < highest:
            highest = prio
    return highest

def write_dma_header(f, peripheral_list, mcu_type, dma_exclude=[],
                     dma_priority='', dma_noshare=''):
    '''write out a DMA resolver header file'''
    global dma_map

    # form a list of DMA priorities
    priority_list = dma_priority.split()

    # sort by priority
    peripheral_list = sorted(peripheral_list, key=lambda x: get_list_index(x, priority_list))

    # form a list of peripherals that can't share
    noshare_list = dma_noshare.split()

    try:
        lib = importlib.import_module(mcu_type)
        dma_map = lib.DMA_Map
    except ImportError:
        print("Unable to find module for MCU %s" % mcu_type)
        sys.exit(1)

    print("Writing DMA map")
    unassigned = []
    curr_dict = {}

    for periph in peripheral_list:
        if periph in dma_exclude:
            continue
        assigned = False
        check_list = []
        if not periph in dma_map:
            print("Unknown peripheral function %s in DMA map for %s" %
                  (periph, mcu_type))
            sys.exit(1)
        for streamchan in dma_map[periph]:
            stream = (streamchan[0], streamchan[1])
            if check_possibility(periph, stream, curr_dict, dma_map,
                                 check_list):
                curr_dict[periph] = stream
                assigned = True
                break
        if assigned == False:
            unassigned.append(periph)

    # now look for shared DMA possibilities
    stream_assign = {}
    for k in curr_dict.keys():
        stream_assign[curr_dict[k]] = [k]

    unassigned_new = unassigned[:]
    for periph in unassigned:
        share_possibility = []
        for streamchan in dma_map[periph]:
            stream = (streamchan[0], streamchan[1])
            share_ok = True
            for periph2 in stream_assign[stream]:
                if not can_share(periph, noshare_list) or not can_share(periph2, noshare_list):
                    share_ok = False
            if share_ok:
                share_possibility.append(stream)
        if share_possibility:
            # sort the possible sharings so minimise impact on high priority streams
            share_possibility = sorted(share_possibility, key=lambda x: get_sharing_priority(stream_assign[x], priority_list))
            # and take the one with the least impact (lowest value for highest priority stream share)
            stream = share_possibility[-1]
            if debug:
                print("Sharing %s on %s with %s" % (periph, stream,
                                                    stream_assign[stream]))
            curr_dict[periph] = stream
            stream_assign[stream].append(periph)
            unassigned_new.remove(periph)
    unassigned = unassigned_new

    f.write("\n\n// auto-generated DMA mapping from dma_resolver.py\n")

    if unassigned:
        f.write(
            "\n// Note: The following peripherals can't be resolved for DMA: %s\n\n"
            % unassigned)

    for key in sorted(curr_dict.keys()):
        stream = curr_dict[key]
        shared = ''
        if len(stream_assign[stream]) > 1:
            shared = ' // shared %s' % ','.join(stream_assign[stream])
        f.write("#define %-30s STM32_DMA_STREAM_ID(%u, %u)%s\n" %
                (chibios_dma_define_name(key)+'STREAM', curr_dict[key][0],
                 curr_dict[key][1], shared))
        for streamchan in dma_map[key]:
            if stream == (streamchan[0], streamchan[1]):
                f.write("#define %-30s %u\n" %
                        (chibios_dma_define_name(key)+'CHAN', streamchan[2]))
                break

# now generate UARTDriver.cpp DMA config lines
    f.write("\n\n// generated UART DMA configuration lines\n")
    for u in range(1, 9):
        key = None
        if 'USART%u_TX' % u in peripheral_list:
            key = 'USART%u' % u
        if 'UART%u_TX' % u in peripheral_list:
            key = 'UART%u' % u
        if 'USART%u_RX' % u in peripheral_list:
            key = 'USART%u' % u
        if 'UART%u_RX' % u in peripheral_list:
            key = 'UART%u' % u
        if key is None:
            continue
        f.write("#define STM32_%s_RX_DMA_CONFIG " % key)
        if key + "_RX" in curr_dict:
            f.write(
                "true, STM32_UART_%s_RX_DMA_STREAM, STM32_%s_RX_DMA_CHN\n" %
                (key, key))
        else:
            f.write("false, 0, 0\n")
        f.write("#define STM32_%s_TX_DMA_CONFIG " % key)
        if key + "_TX" in curr_dict:
            f.write(
                "true, STM32_UART_%s_TX_DMA_STREAM, STM32_%s_TX_DMA_CHN\n" %
                (key, key))
        else:
            f.write("false, 0, 0\n")

if __name__ == '__main__':
    import optparse

    parser = optparse.OptionParser("dma_resolver.py")
    parser.add_option("-M", "--mcu", default=None, help='MCU type')
    parser.add_option(
        "-D", "--debug", action='store_true', help='enable debug')
    parser.add_option(
        "-P",
        "--peripherals",
        default=None,
        help='peripheral list (comma separated)')

    opts, args = parser.parse_args()

    if opts.peripherals is None:
        print("Please provide a peripheral list with -P")
        sys.exit(1)

    if opts.mcu is None:
        print("Please provide a MCU type with -<")
        sys.exit(1)

    debug = opts.debug

    plist = opts.peripherals.split(',')
    mcu_type = opts.mcu

    f = open("dma.h", "w")
    write_dma_header(f, plist, mcu_type)


#coding=utf-8

from random import SystemRandom
from string import ascii_letters, digits, punctuation

from django.core.management.base import BaseCommand, CommandError
from django.conf import settings
from django.contrib.sites.models import Site

from setup.models import Setup

class Command(BaseCommand):
  help = 'Adjusts settings file based on inital config via web interface'

  def handle(self, *args, **options):
    #adjust settings from setup app
    try:
      S = Setup.objects.latest('cfg_date')
      if S.admin_email:
        settings.SERVER_EMAIL = S.admin_email
        self.stdout.write(self.style.WARNING(u'SERVER_EMAIL = "'+S.admin_email+'"'))
      if S.org_name:
        settings.TEMPLATE_CONTENT['meta']['logo']['title'] = S.org_name
        self.stdout.write(self.style.WARNING(u"TEMPLATE_CONTENT['meta']['logo']['title'] = \""+S.org_name+"\""))
        settings.TEMPLATE_CONTENT['meta']['title'] = S.org_name+" - Club Management System (CMS)"
        self.stdout.write(self.style.WARNING(u"TEMPLATE_CONTENT['meta']['title'] = \""+S.org_name+u" - Club Management System (CMS)\""))
        if S.default_email:
          settings.DEFAULT_FROM_EMAIL = S.default_email
          self.stdout.write(self.style.WARNING(u"DEFAULT_FROM_EMAIL = \""+S.default_email+"\""))
      if S.default_footer:
        settings.EMAILS['footer'] = S.default_footer
        self.stdout.write(self.style.WARNING(u"EMAILS['footer'] = \"\"\""+S.default_footer+"\"\"\""))
      if S.org_logo:
        settings.TEMPLATE_CONTENT['meta']['logo']['img'] = S.org_logo
        self.stdout.write(self.style.WARNING(u"TEMPLATE_CONTENT['meta']['logo']['img'] = \""+S.org_logo+"\""))
    except Setup.DoesNotExist:
      self.stdout.write(self.style.ERROR(u"No setup data found in database. First you need to configure the system via the webinterface."))

    self.stdout.write(self.style.SUCCESS('Settings adjusted successfully.'))


import re
import collections
import logging
from nltk.corpus import stopwords
import pandas as pd
import gensim.models.word2vec as w2v
import multiprocessing
import os
import sklearn.manifold
import seaborn as sns

# def tokenizer(text, spliter=" "):
#     newStr = ""
#     if text is not None and len(text)> 0 :
#         newStr = text.split(spliter)
#     return newStr
#
# def clean(text):
#     filter = [',', '?', '"', '-', '\n', '”', '“', ';', '—', '!', '\'', '.', '\t', '\ufeff']
#     for f in filter:
#         if f in text:
#             text = text.replace(f, "").strip()
#
#             print(text)
#     return text
logging.basicConfig(format='%(asctime)s : %(levelname)s : %(message)s', level=logging.INFO)

def commonwords_and_dataframe(stop_words,words):
    wordcount = {}  # dictionary
    for word in words:
        if word not in stop_words:
            if word not in wordcount:
                wordcount[word] = 1
            else:
                wordcount[word] += 1    # count +1 when it occurs again

    n_print = 100   # number of most common words
    word_counter = collections.Counter(wordcount)

    for word, count in word_counter.most_common(n_print):
        print(word, ": ", count,"times, ", (count/len(words))*100)

    lst = word_counter.most_common(n_print)
    df = pd.DataFrame(lst, columns=['Word', 'Count'])
    df.plot.bar(x='Word', y='Count')

def cleantxt(text, lang):

    if lang is 'english':
        stop_words = set(stopwords.words('english'))  #using nltk.corpus lib
        # print(stop_words)  #print stop words
        words = " ".join(re.findall("[a-zA-Z]+", text)).split() #using rex extract only char
        print(type(words))

    elif lang is 'turkish':
        stop_words = set(stopwords.words('turkish'))    #using nltk.corpus lib
        words = " ".join(re.findall("[abcçdefgğhiıjklmnoöprsştuüvyzABCÇDEFGĞHIİJKLMNOÖPRSŞTUÜVYZ]+", text)).split()
    else:
        stop_words = set(stopwords.words(lang))     # using nltk.corpus lib- for supported languages >> stopwords.fileids()
        words = " ".join(re.findall("[a-zA-Z]+", text)).split()  # it can be modified depend on the language

    commonwords_and_dataframe(stop_words,words)

    words = [x.lower() for x in words]      # lowercase all strings
    words = [w for w in words if not w in stop_words]       # extract stop words

    return words

def init(filename, language):

    texts = open(filename, encoding='utf-8').read().lower()  # open file
    raw_text = cleantxt(texts, language)    # clean text
    print(type(raw_text))  # <class 'list'>
    print(len(raw_text))   # 104_899
    keyword_set = set(raw_text)
    print(len(keyword_set))  # 10_908

    return keyword_set

_file_dir = ['C:\MyProjects\PythonProjects\ClassifyNews']
_file_name = ['\hp1.txt', '\hp2.txt', '\hp3.txt', '\hp4.txt', '\hp5.txt', '\hp6.txt', '\hp7.txt']
_file_names = []
for f in _file_name:
    for i in _file_dir:
        _file_names.append(i + f)

print(_file_names)

# _file_name1 = "C:\MyProjects\PythonProjects\Keras\hp1.txt"
# _file_name2 = "C:\MyProjects\PythonProjects\Keras\hp2.txt"
_language = 'english'

keywords = []
# keyword = init(_file_name, _language)
for _f_name in _file_names:
    print(_f_name)
    k = init(_f_name, _language)
    k = list(k)
    keywords.append(k)

# print("keyworddddd")
print(keywords)

num_features = 300
min_word_count = 3
num_workers = multiprocessing.cpu_count()
context_size = 7
downsampling = 1e-3

seed = 1

harry2vec = w2v.Word2Vec(
    sg=1,
    seed=seed,
    workers=num_workers,
    size=num_features,
    min_count=min_word_count,
    window=context_size,
    sample=downsampling
)

harry2vec.build_vocab(keywords)
print("Word2Vec vocabulary length:", len(harry2vec.wv.vocab))

harry2vec.train(keywords, epochs=harry2vec.iter, total_examples=harry2vec.corpus_count)

if not os.path.exists("trained"):
    os.makedirs("trained")

harry2vec.save(os.path.join("trained", "harry2vec.w2v"))
# num_steps=30
#
# batch_size=20
#
# hidden_size=500
#
#
# model = Sequential()
# model.add(Embedding(keywords, hidden_size, input_length=num_steps))
# model.add(LSTM(hidden_size, return_sequences=True))
# model.add(LSTM(hidden_size, return_sequences=True))
# model.add(TimeDistributed(Dense(keywords)))
# model.add(Activation('softmax'))
# model.summary()
#
# model = sklearn.manifold.TSNE(n_components=2, random_state=0)
# np.set_printoptions(suppress=True)
# Y=model.fit_transform(train_data_features)
#
# plt.scatter(Y[:, 0], Y[:, 1], c=clustering_ result, s=290,alpha=.5)
# plt.show()

print("control-1")
tsne = sklearn.manifold.TSNE(n_components=3, random_state=0)
print("control-2")
all_vector_matrix = harry2vec.wv.syn0
print("control-3")
all_vector_matrix_2d = tsne.fit_transform(all_vector_matrix)
print("control-4")

print(harry2vec.most_similar("family"))

points = pd.DataFrame(
    [
        (word, coords[0], coords[1])
        for word, coords in [
            (word, all_vector_matrix_2d[harry2vec.wv.vocab[word].index])
            for word in harry2vec.wv.vocab]
    ],
    columns=["word", "x", "y"]
)

print(points.head(10))

sns.set_context("poster")
points.plot.scatter("x", "y", s=10, figsize=(20, 12))

def plot_region(x_bounds, y_bounds):

    slice = points[
        (x_bounds[0] <= points.x) & (points.x <= x_bounds[1]) &
        (y_bounds[0] <= points.y) & (points.y <= y_bounds[1])
        ]
    ax = slice.plot.scatter("x", "y", s=35, figsize=(10, 8))
    for i, point in slice.iterrows():
        ax.text(point.x + 0.005, point.y + 0.005, point.word, fontsize=11)

print(points.x, points.y)
plot_region(x_bounds=(10.0, 26.2), y_bounds=(-24.0, 26.4))


import http.client
import json
from dotenv import load_dotenv
import os

def translate() :
	load_dotenv()

	text = str(input("Type: "))

	spaceReplace = text.replace(" ", "%20")
	commaReplace = spaceReplace.replace(",", "%2C")
	formatedText = commaReplace.replace('"', "%22")

	HOST = os.getenv("X_RAPIDAPI_HOST")
	API_KEY = os.getenv("X_RAPIDAPI_KEY")

	conn = http.client.HTTPSConnection(HOST)

	payload = f"q={formatedText}&source=en&target=pt"

	headers = {
			'content-type': "application/x-www-form-urlencoded",
			'accept-encoding': "application/gzip",
			'x-rapidapi-key': API_KEY,
			'x-rapidapi-host': HOST
			}

	conn.request("POST", "/language/translate/v2", payload, headers)

	res = conn.getresponse()
	data = res.read()
	dictData = json.loads(data.decode("utf-8"))
	translatedText = dictData['data']['translations'][0]['translatedText']

	print(translatedText)


from django.urls import path

from . import views

app_name = 'user'

urlpatterns = [
    path('usear/create/', views.CreateUserView.as_view(), name='create'),
    path('user/token/', views.CreateTokenView.as_view(), name='token'),
    path('user/me/', views.ManageUserView.as_view(), name='me'),
]


import requests

url = "https://safeut.test.med.utah.edu/apidemo/RestService/Quote"

req = requests.get(url)
print("Status code:", req.status_code)

return_value = req.json()
print(return_value)

# If there is anything the nonconformist hates worse than a conformist, it’s another nonconformist who doesn’t conform to the prevailing standard of nonconformity.


from kontroller import Controller
from menu import Menu
import os
clear = lambda: os.system("cls")
kontroller = Controller()

menu = Menu()
menu.AddMenuItem("Добавить домашнее животное",kontroller.add_pet)
menu.AddMenuItem("Добавить хозяина",kontroller.add_owner)
menu.AddMenuItem("Показать всех петов владельца",kontroller.show_all_owner_pet)
menu.AddMenuItem("Найти всех животных по виду ",kontroller.show_pet_by_type)
menu.AddMenuItem("Тест",kontroller.test)
menu.AddMenuItem("Выйти из программы",exit)

while True:
    menu.Show()
    menu.GetValueFromInput("->")()
    input("Нажмите Enter для продолжения ")
    clear()
# конец файла


'''
+ wir wollen Zahlen vom user einlesen
+ ermitteln wie oft ein Zahl eingegeben wurde, bis er 0 eingibt

Usereingabe
3,4,5,2,3,4,3,0

Ausgabe
3: 3
4: 2
5: 1

( + das ganze automatisch testbar machen )
'''

import sys
from pprint import pprint
from pathlib import Path
sys.path.append(str(Path('.').absolute().parent))
from WifiUtil import get_user_number_input

def count_occurences(data):
    count = {}
    for d in data:
        if d in count:
            count[d] += 1
        else:
            count[d] = 1
    return count

if __name__ == '__main__':
    user_data = []
    while True:
        user_input = get_user_number_input(prompt = 'Bitte Zahl eingeben: ')
        if user_input == 0:
            break
        user_data.append(user_input)

    count = count_occurences(user_data)
    pprint(count)