import typing
import warnings
from tequila import TequilaWarning
from .qc_base import QuantumChemistryBase
from .chemistry_tools import ParametersQC, NBodyTensor
from .madness_interface import QuantumChemistryMadness
SUPPORTED_QCHEMISTRY_BACKENDS = ["base", "psi4", "madness", "pyscf"]
INSTALLED_QCHEMISTRY_BACKENDS = {"base": QuantumChemistryBase, "madness": QuantumChemistryMadness}
try:
    from .psi4_interface import QuantumChemistryPsi4
    INSTALLED_QCHEMISTRY_BACKENDS["psi4"] = QuantumChemistryPsi4
except ImportError:
    pass
try:
    from .pyscf_interface import QuantumChemistryPySCF
    INSTALLED_QCHEMISTRY_BACKENDS["pyscf"] = QuantumChemistryPySCF
except ImportError:
    pass
[docs]
def show_available_modules():
    print("Available QuantumChemistry Modules:")
    for k in INSTALLED_QCHEMISTRY_BACKENDS.keys():
        print(k) 
[docs]
def show_supported_modules():
    print(SUPPORTED_QCHEMISTRY_BACKENDS) 
[docs]
def Molecule(geometry: str = None,
             basis_set: str = None,
             transformation: typing.Union[str, typing.Callable] = None,
             orbital_type: str = None,
             backend: str = None,
             guess_wfn=None,
             name: str = None,
             *args,
             **kwargs) -> QuantumChemistryBase:
    """
    Parameters
    ----------
    geometry
        molecular geometry as string or as filename (needs to be in xyz format with .xyz ending)
    basis_set
        quantum chemistry basis set (sto-3g, cc-pvdz, etc)
    transformation
        The Fermion to Qubit Transformation (jordan-wigner, bravyi-kitaev, bravyi-kitaev-tree and whatever OpenFermion supports)
    backend
        quantum chemistry backend (psi4, pyscf)
    guess_wfn
        pass down a psi4 guess wavefunction to start the scf cycle from
        can also be a filename leading to a stored wavefunction
    name
        name of the molecule, if not given it's auto-deduced from the geometry
        can also be done vice versa (i.e. geometry is then auto-deduced to name.xyz)
    args
    kwargs
    Returns
    -------
        The Fermion to Qubit Transformation (jordan-wigner, bravyi-kitaev, bravyi-kitaev-tree and whatever OpenFermion supports)
    """
    # failsafe for common mistake
    if "basis" in kwargs:
        warnings.warn("called molecule with keyword \"basis={0}\" converting it to \"basis_set={0}\"".format(kwargs["basis"]), TequilaWarning)
        if basis_set is not None:
            warnings.warn("did not convert as \"basis_set={}\" was already given".format(basis_set), TequilaWarning)
        basis_set=kwargs["basis"]
    
    keyvals = {}
    for k, v in kwargs.items():
        if k in ParametersQC.__dict__.keys():
            keyvals[k] = v
    if "parameters" in kwargs:
        parameters = kwargs["parameters"]
        kwargs.pop("parameters")
    else:
        parameters = ParametersQC(name=name, geometry=geometry, basis_set=basis_set, multiplicity=1, **keyvals)
    integrals_provided = all([key in kwargs for key in ["one_body_integrals", "two_body_integrals"]])
    if integrals_provided and backend is None:
        backend = "base"
    if backend is None:
        if basis_set is None or basis_set.lower() in ["madness", "mra", "pno"]:
            backend = "madness"
            basis_set = "mra"
            parameters.basis_set = basis_set
            if orbital_type is not None and orbital_type.lower() not in ["pno", "mra-pno"]:
                warnings.warn("only PNOs supported as orbital_type without basis set. Setting to pno - You gave={}".format(orbital_type), TequilaWarning)
            orbital_type = "pno"
        else:
            if orbital_type is not None and orbital_type.lower() not in ["hf", "native"]:
                warnings.warn("only hf and native supported as orbital_type with basis-set. Setting to hf - You gave={}".format(orbital_type), TequilaWarning)
                orbital_type = "hf"
            if orbital_type is None:
                orbital_type = "hf"
            if "psi4" in INSTALLED_QCHEMISTRY_BACKENDS:
                backend = "psi4"
            elif "pyscf" in INSTALLED_QCHEMISTRY_BACKENDS:
                backend = "pyscf"
            else:
                raise Exception("No quantum chemistry backends installed on your system")
    
    elif backend == "base":
        if not integrals_provided:
            raise Exception("No quantum chemistry backends installed on your system\n"
                            "To use the base functionality you need to pass the following tensors via keyword\n"
                            "one_body_integrals, two_body_integrals\n")
        else:
            backend = "base"
    if backend not in SUPPORTED_QCHEMISTRY_BACKENDS:
        raise Exception(str(backend) + " is not (yet) supported by tequila")
    if backend not in INSTALLED_QCHEMISTRY_BACKENDS:
        raise Exception(str(backend) + " was not found on your system")
    if guess_wfn is not None and backend != 'psi4':
        raise Exception("guess_wfn only works for psi4")
    if basis_set is None and backend.lower() not in ["base", "madness"] and not integrals_provided:
        raise Exception("no basis_set or integrals provided for backend={}".format(backend))
    elif basis_set is None:
        basis_set = "custom"
        parameters.basis_set = basis_set
    return INSTALLED_QCHEMISTRY_BACKENDS[backend.lower()](parameters=parameters, transformation=transformation, orbital_type=orbital_type,
                                                          guess_wfn=guess_wfn, *args, **kwargs) 
[docs]
def MoleculeFromTequila(mol, transformation=None, backend=None, *args, **kwargs):
    c, h, g = mol.get_integrals()
    parameters = mol.parameters
    if backend is None:
        if "pyscf" in INSTALLED_QCHEMISTRY_BACKENDS:
            backend = "pyscf"
        else:
            backend = "base"
    if transformation is None:
        transformation = mol.transformation
    return INSTALLED_QCHEMISTRY_BACKENDS[backend.lower()](parameters=parameters, transformation=transformation,
                                                          n_electrons=mol.n_electrons, one_body_integrals=h,
                                                          two_body_integrals=g, nuclear_repulsion=c, *args, **kwargs) 
[docs]
def MoleculeFromOpenFermion(molecule,
                            transformation: typing.Union[str, typing.Callable] = None,
                            backend: str = None,
                            *args,
                            **kwargs) -> QuantumChemistryBase:
    """
    Initialize a tequila Molecule directly from an openfermion molecule object
    Parameters
    ----------
    molecule
        The openfermion molecule
    transformation
        The Fermion to Qubit Transformation (jordan-wigner, bravyi-kitaev, bravyi-kitaev-tree and whatever OpenFermion supports)
    backend
        The quantum chemistry backend, can be None in this case
    Returns
    -------
        The tequila molecule
    """
    if backend is None:
        return QuantumChemistryBase.from_openfermion(molecule=molecule, transformation=transformation, *args, **kwargs)
    else:
        INSTALLED_QCHEMISTRY_BACKENDS[backend].from_openfermion(molecule=molecule, transformation=transformation, *args,
                                                                **kwargs) 
# needs pyscf (handeled in call)
from .orbital_optimizer import optimize_orbitals