# Path: paratemp/sim_setup/molecule.py
# class Molecule(object):
#     """
#     Molecule class will make a GAFF-parameterized Structure from an input
#     """
# 
#     def __init__(
#         self,
#         geometry: Union[str, Path],
#         charge: int = 0,
#         name: str = None,
#         resname: str = "MOL",
#     ):
#         log.debug("Initializing Molecule with {}".format(geometry))
#         self._input_geo_path = Path(geometry)
#         self._name = self._input_geo_path.stem if name is None else name
#         self.resname = resname
#         self._directory = Path(self._name).resolve()
#         self._directory.mkdir(exist_ok=True)
#         shutil.copy(self._input_geo_path, self._directory)
#         self.charge = int(charge)
#         self._parameterized = False
#         self._gro = None
#         self._top = None
#         self._ptop = None
#         self.atom_types = None
# 
#     @classmethod
#     def from_make_mol_inputs(cls, mol_inputs):
#         return cls(**mol_inputs)
# 
#     @classmethod
#     def assisted(cls):
#         return cls(**make_mol_inputs())
# 
#     def parameterize(self):
#         # could take keywords for FF
#         # could use charges from QM calc
#         # TODO convert from whatever to PDB, MDL, or MOL2
#         log.debug("Parameterizing {} with acpype".format(self._name))
#         env_to_load = self._get_amber_env()
#         cl = shlex.split(
#             "acpype -i {} "
#             "-o gmx "
#             "-n {} "
#             "-c user "
#             "-b {} ".format(self._input_geo_path.resolve(), self.charge, self._name)
#         )
#         log.warning("Running acpype; this may take a few minutes")
#         proc = self._run_in_dir(cl, env=env_to_load)
#         log.info("acpype said:\n {}".format(proc.stdout))
#         proc.check_returncode()
#         ac_dir = self._directory / "{}.acpype".format(self._name)
#         gro = ac_dir / "{}_GMX.gro".format(self._name)
#         top = ac_dir / "{}_GMX.top".format(self._name)
#         if not gro.is_file() or not top.is_file():
#             mes = "gro or top file not created in {}".format(ac_dir)
#             log.error(mes)
#             raise FileNotFoundError(mes)
#         self._gro = gro
#         self._top = top
#         ptop = parmed.gromacs.GromacsTopologyFile(str(top), xyz=str(gro))
#         self._ptop = ptop
#         for res in ptop.residues:
#             res.name = self.resname
#         self.atom_types = set(a.type for a in ptop.atoms)
#         ptop.write(str(self._directory / "{}.top".format(self._name)))
#         ptop.save(str(self._directory / "{}.gro".format(self._name)))
#         log.info("Wrote top and gro files in {}".format(self._directory))
#         self._parameterized = True
# 
#     @staticmethod
#     def _get_amber_env() -> Dict[str, str]:
#         log.info("Using special environment variables for Amber executables")
#         amber_env_stream = pkg_resources.resource_stream(
#             __name__, "SimpleSim_data/amber_env.json"
#         )  # type: TextIOBase
#         amber_env = json.load(amber_env_stream)
#         curr_env = dict(os.environ)
#         curr_env.update(amber_env)
#         try:
#             conda_prefix = Path(curr_env["CONDA_PREFIX"])
#             python_bin = conda_prefix / "bin"
#         except KeyError:
#             import sys
# 
#             python_bin = Path(sys.executable).parent
#         curr_env["PATH"] += os.pathsep + str(python_bin)
#         return curr_env
# 
#     @property
#     def topology(self) -> parmed.Structure:
#         return self._ptop
# 
#     @property
#     def directory(self) -> Path:
#         return self._directory
# 
#     @property
#     def name(self) -> str:
#         return self._name
# 
#     def _run_in_dir(self, cl, **kwargs) -> subprocess.CompletedProcess:
#         with cd(self._directory):
#             proc = subprocess.run(
#                 cl,
#                 stdout=subprocess.PIPE,
#                 stderr=subprocess.STDOUT,
#                 universal_newlines=True,
#                 **kwargs
#             )
#         return proc
# 
#     def __repr__(self):
#         return "<{} Molecule; parameterized: {}>".format(self.name, self._parameterized)


import logging
import re
import parmed
import pkg_resources
from pathlib import Path
from typing import Dict
from . import Molecule
    """
    The System class is intended to combine several Molecules.

    It will add the molecules together into one box and optionally shift the
    molecules in the z direction to make sure the molecules are no longer
    overlapping.
    It can also optionally add GBSA implicit solvation parameters based on
    Amber03.

    :param args: molecules to combine into this object
    :param name: name of the system
    :param shift: If True, the molecules will be moved to be non-overlapping
    :param spacing: distance to put between the molecules (in angstroms)
    :param include_gbsa: If True, GBSA parameters for implicit solvation will
        be included in the topology file
    :param box_length: Length of cubic box (in angstroms)
    """

    def __init__(
        self,
        *args: Molecule,
        name: str = "default",
        shift: bool = True,
        spacing: float = 2.0,
        include_gbsa: bool = True,
        box_length: float = 25.0
    ):
        log.debug("Initializing System with {} Molecules".format(len(args)))
        self._name = name
        for arg in args:

# Path: paratemp/sim_setup/molecule.py
# class Molecule(object):
#     """
#     Molecule class will make a GAFF-parameterized Structure from an input
#     """
# 
#     def __init__(
#         self,
#         geometry: Union[str, Path],
#         charge: int = 0,
#         name: str = None,
#         resname: str = "MOL",
#     ):
#         log.debug("Initializing Molecule with {}".format(geometry))
#         self._input_geo_path = Path(geometry)
#         self._name = self._input_geo_path.stem if name is None else name
#         self.resname = resname
#         self._directory = Path(self._name).resolve()
#         self._directory.mkdir(exist_ok=True)
#         shutil.copy(self._input_geo_path, self._directory)
#         self.charge = int(charge)
#         self._parameterized = False
#         self._gro = None
#         self._top = None
#         self._ptop = None
#         self.atom_types = None
# 
#     @classmethod
#     def from_make_mol_inputs(cls, mol_inputs):
#         return cls(**mol_inputs)
# 
#     @classmethod
#     def assisted(cls):
#         return cls(**make_mol_inputs())
# 
#     def parameterize(self):
#         # could take keywords for FF
#         # could use charges from QM calc
#         # TODO convert from whatever to PDB, MDL, or MOL2
#         log.debug("Parameterizing {} with acpype".format(self._name))
#         env_to_load = self._get_amber_env()
#         cl = shlex.split(
#             "acpype -i {} "
#             "-o gmx "
#             "-n {} "
#             "-c user "
#             "-b {} ".format(self._input_geo_path.resolve(), self.charge, self._name)
#         )
#         log.warning("Running acpype; this may take a few minutes")
#         proc = self._run_in_dir(cl, env=env_to_load)
#         log.info("acpype said:\n {}".format(proc.stdout))
#         proc.check_returncode()
#         ac_dir = self._directory / "{}.acpype".format(self._name)
#         gro = ac_dir / "{}_GMX.gro".format(self._name)
#         top = ac_dir / "{}_GMX.top".format(self._name)
#         if not gro.is_file() or not top.is_file():
#             mes = "gro or top file not created in {}".format(ac_dir)
#             log.error(mes)
#             raise FileNotFoundError(mes)
#         self._gro = gro
#         self._top = top
#         ptop = parmed.gromacs.GromacsTopologyFile(str(top), xyz=str(gro))
#         self._ptop = ptop
#         for res in ptop.residues:
#             res.name = self.resname
#         self.atom_types = set(a.type for a in ptop.atoms)
#         ptop.write(str(self._directory / "{}.top".format(self._name)))
#         ptop.save(str(self._directory / "{}.gro".format(self._name)))
#         log.info("Wrote top and gro files in {}".format(self._directory))
#         self._parameterized = True
# 
#     @staticmethod
#     def _get_amber_env() -> Dict[str, str]:
#         log.info("Using special environment variables for Amber executables")
#         amber_env_stream = pkg_resources.resource_stream(
#             __name__, "SimpleSim_data/amber_env.json"
#         )  # type: TextIOBase
#         amber_env = json.load(amber_env_stream)
#         curr_env = dict(os.environ)
#         curr_env.update(amber_env)
#         try:
#             conda_prefix = Path(curr_env["CONDA_PREFIX"])
#             python_bin = conda_prefix / "bin"
#         except KeyError:
#             import sys
# 
#             python_bin = Path(sys.executable).parent
#         curr_env["PATH"] += os.pathsep + str(python_bin)
#         return curr_env
# 
#     @property
#     def topology(self) -> parmed.Structure:
#         return self._ptop
# 
#     @property
#     def directory(self) -> Path:
#         return self._directory
# 
#     @property
#     def name(self) -> str:
#         return self._name
# 
#     def _run_in_dir(self, cl, **kwargs) -> subprocess.CompletedProcess:
#         with cd(self._directory):
#             proc = subprocess.run(
#                 cl,
#                 stdout=subprocess.PIPE,
#                 stderr=subprocess.STDOUT,
#                 universal_newlines=True,
#                 **kwargs
#             )
#         return proc
# 
#     def __repr__(self):
#         return "<{} Molecule; parameterized: {}>".format(self.name, self._parameterized)


# Path: paratemp/sim_setup/system.py
import logging
import re
import parmed
import pkg_resources
from pathlib import Path
from typing import Dict
from . import Molecule

    """
    The System class is intended to combine several Molecules.

    It will add the molecules together into one box and optionally shift the
    molecules in the z direction to make sure the molecules are no longer
    overlapping.
    It can also optionally add GBSA implicit solvation parameters based on
    Amber03.

    :param args: molecules to combine into this object
    :param name: name of the system
    :param shift: If True, the molecules will be moved to be non-overlapping
    :param spacing: distance to put between the molecules (in angstroms)
    :param include_gbsa: If True, GBSA parameters for implicit solvation will
        be included in the topology file
    :param box_length: Length of cubic box (in angstroms)
    """

    def __init__(
        self,
        *args: Molecule,
        name: str = "default",
        shift: bool = True,
        spacing: float = 2.0,
        include_gbsa: bool = True,
        box_length: float = 25.0
    ):
        log.debug("Initializing System with {} Molecules".format(len(args)))
        self._name = name
        for arg in args:

            if not isinstance(arg, Molecule):