# =======================
# Python internal package
# =======================
# P
import pathlib

# ================
# External package
# ================
# N
import numpy

# P
import pytest

# ==============
# Module package
# ==============
# P
from src.strange.parser.configuration import (
    Configuration,
)
from src.strange.parser.molecule import (
    Molecule,
    MoleculeParser,
)
from src.strange.pharmacophore import (
    DecoratorNormalVector,
    PharmacophoreFactory,
)

# U
from src.strange.utils.generic import (
    CsvWriter,
)


def normal(
    centre: numpy.ndarray,
    edge_1: numpy.ndarray,
    edge_2: numpy.ndarray,
) -> numpy.ndarray:
    """Compute a normalized normal vector from three coordinates.

    Parameters
    ----------
    centre : `numpy.ndarray`
        Coordinates of the reference centre point.

    edge_1 : `numpy.ndarray`
        Coordinates of the first point defining the plane.

    edge_2 : `numpy.ndarray`
        Coordinates of the second point defining the plane.

    Returns
    -------
    `numpy.ndarray`
        The normalized normal vector.
    """
    return DecoratorNormalVector._DecoratorNormalVector__normal(
        None, centre, edge_1, edge_2
    )


@pytest.mark.parametrize(
    "excepted_normal, centre, edge_1, edge_2",
    [
        [
            numpy.array([1.0, -9.0, 4.0]),
            numpy.array([1.0, 2.0, 3.0]),
            numpy.array([4.0, 1.0, 0.0]),
            numpy.array([2.0, 3.0, 5.0]),
        ],
        [
            numpy.array([0.0, 0.0, -1.0]),
            numpy.array([0.0, 0.0, 0.0]),
            numpy.array([0.0, 1.0, 0.0]),
            numpy.array([1.0, 0.0, 0.0]),
        ],
        [
            numpy.array([12.0, -24.0, 12.0]),
            numpy.array([1.0, 2.0, 3.0]),
            numpy.array([-1.0, -2.0, -3.0]),
            numpy.array([7.0, 8.0, 9.0]),
        ],
        [
            numpy.array([1_512.0, 3_528.0, 4_536.0]),
            numpy.array([72.0, -82.0, 72.0]),
            numpy.array([-100.0, 200.0, -90.0]),
            numpy.array([12.0, -10.0, 36.0]),
        ],
    ],
)
def test_normal_unit_vector(
    excepted_normal: numpy.ndarray,
    centre: numpy.ndarray,
    edge_1: numpy.ndarray,
    edge_2: numpy.ndarray,
) -> None:
    """Test the normal vector computation to a plane.

    Parameters
    ----------
    excepted_normal : `numpy.ndarray`
        The expected (non-normalized) normal vector.

    centre : `numpy.ndarray`
        Coordinates of the centre point.

    edge_1 : `numpy.ndarray`
        Coordinates of the first plane-defining point.

    edge_2 : `numpy.ndarray`
        Coordinates of the second plane-defining point.
    """
    unit_excepted = excepted_normal / numpy.linalg.norm(excepted_normal)

    assert numpy.all(unit_excepted == normal(centre, edge_1, edge_2))


@pytest.fixture
def __computed_pharmacophore() -> str:
    """Compute the pharmacophore CSV output for a reference molecule.

    Returns
    -------
    `str`
        The computed pharmacophore CSV content.
    """
    configuration: Configuration = Configuration()

    molecule: Molecule = MoleculeParser(
        pathlib.Path("tests/data/celecoxib/molecule.sdf")
    )
    factory: PharmacophoreFactory = PharmacophoreFactory(
        configuration["miscellaneous"]
    )
    collection_list: list[dict] = factory.get_collection(molecule)

    csv = CsvWriter(
        [collection_list], "pharmacophore", configuration["miscellaneous"]
    )

    return str(csv)


@pytest.fixture
def __good_csv() -> str:
    """Load the reference pharmacophore CSV output.

    Returns
    -------
    `str`
        The expected pharmacophore CSV content.
    """
    csv: str = ""

    with open(
        "tests/data/celecoxib/pharmacophore.csv", "r", encoding="utf-8"
    ) as file:
        csv = "".join(file.readlines())

    return csv


def test_pharmacophore_csv(
    __computed_pharmacophore: str, __good_csv: str
) -> None:
    """Validate pharmacophore CSV serialization.

    Parameters
    ----------
    __computed_pharmacophore : `str`
        The CSV output generated by the pharmacophore pipeline.

    __good_csv : `str`
        The reference CSV content.
    """
    assert __computed_pharmacophore == __good_csv
