import pytest

from MDAnalysis.analysis.base import AnalysisBase, AnalysisFromFunction
from MDAnalysisTests.analysis.test_base import (
    FrameAnalysis,
    IncompleteAnalysis,
    OldAPIAnalysis,
)
from MDAnalysis.analysis.rms import RMSD, RMSF
from MDAnalysis.analysis.dihedrals import Dihedral, Ramachandran, Janin
from MDAnalysis.analysis.bat import BAT
from MDAnalysis.analysis.gnm import GNMAnalysis
from MDAnalysis.analysis.dssp.dssp import DSSP
from MDAnalysis.analysis.hydrogenbonds.hbond_analysis import (
    HydrogenBondAnalysis,
)
from MDAnalysis.analysis.nucleicacids import NucPairDist
from MDAnalysis.analysis.contacts import Contacts
from MDAnalysis.analysis.density import DensityAnalysis
from MDAnalysis.lib.util import is_installed


def params_for_cls(cls, exclude: list[str] = None):
    """
    This part contains fixtures for simultaneous testing
    of all available (=installed & supported) backends
    for analysis subclasses.

    If for some reason you want to limit backends,
    simply pass "exclude: list[str]" to the function
    that parametrizes fixture.

    Parameters
    ----------
    exclude : list[str], optional
        list of backends to exclude from parametrization, by default None

    Returns
    -------
    dict
        dictionary with all tested keyword combinations for the run
    """
    exclude = [] if exclude is None else exclude
    possible_backends = cls.get_supported_backends()
    installed_backends = [
        b for b in possible_backends if is_installed(b) and b not in exclude
    ]

    params = [
        pytest.param(
            {"backend": backend, "n_workers": nproc},
        )
        for backend in installed_backends
        for nproc in (2,)
        if backend != "serial"
    ]
    params.extend([{"backend": "serial"}])
    return params


@pytest.fixture(scope="module", params=params_for_cls(FrameAnalysis))
def client_FrameAnalysis(request):
    return request.param


@pytest.fixture(scope="module", params=params_for_cls(AnalysisBase))
def client_AnalysisBase(request):
    return request.param


@pytest.fixture(scope="module", params=params_for_cls(AnalysisFromFunction))
def client_AnalysisFromFunction(request):
    return request.param


@pytest.fixture(
    scope="module",
    params=params_for_cls(AnalysisFromFunction, exclude=["multiprocessing"]),
)
def client_AnalysisFromFunctionAnalysisClass(request):
    return request.param


@pytest.fixture(scope="module", params=params_for_cls(IncompleteAnalysis))
def client_IncompleteAnalysis(request):
    return request.param


@pytest.fixture(scope="module", params=params_for_cls(OldAPIAnalysis))
def client_OldAPIAnalysis(request):
    return request.param


# MDAnalysis.analysis.rms


@pytest.fixture(scope="module", params=params_for_cls(RMSD))
def client_RMSD(request):
    return request.param


@pytest.fixture(scope="module", params=params_for_cls(RMSF))
def client_RMSF(request):
    return request.param


# MDAnalysis.analysis.dihedrals


@pytest.fixture(scope="module", params=params_for_cls(Dihedral))
def client_Dihedral(request):
    return request.param


@pytest.fixture(scope="module", params=params_for_cls(Ramachandran))
def client_Ramachandran(request):
    return request.param


@pytest.fixture(scope="module", params=params_for_cls(Janin))
def client_Janin(request):
    return request.param


# MDAnalysis.analysis.gnm


@pytest.fixture(scope="module", params=params_for_cls(GNMAnalysis))
def client_GNMAnalysis(request):
    return request.param


# MDAnalysis.analysis.bat


@pytest.fixture(scope="module", params=params_for_cls(BAT))
def client_BAT(request):
    return request.param


# MDAnalysis.analysis.dssp.dssp


@pytest.fixture(scope="module", params=params_for_cls(DSSP))
def client_DSSP(request):
    return request.param


# MDAnalysis.analysis.hydrogenbonds


@pytest.fixture(scope="module", params=params_for_cls(HydrogenBondAnalysis))
def client_HydrogenBondAnalysis(request):
    return request.param


# MDAnalysis.analysis.nucleicacids


@pytest.fixture(scope="module", params=params_for_cls(NucPairDist))
def client_NucPairDist(request):
    return request.param


# MDAnalysis.analysis.contacts


@pytest.fixture(scope="module", params=params_for_cls(Contacts))
def client_Contacts(request):
    return request.param


# MDAnalysis.analysis.density


@pytest.fixture(scope="module", params=params_for_cls(DensityAnalysis))
def client_DensityAnalysis(request):
    return request.param
