"""Module containing big dataclass to setup everything."""

# ========================
# Python internal packages
# ========================
# D
import dataclasses

# ========================
# Python external packages
# ========================
# M
import matplotlib
from matplotlib import pyplot

# ==============
# Module package
# ==============
# G
from .generic import (
    check_typing,
    DataClass,
    PharmacophoreFamily,
    InteractionType,
)

# M
from .messenger import (
    Error,
)


@dataclasses.dataclass
class InteractionConfiguration:
    """Configuration parameters for pharmacophore-based interaction detection.

    Parameters
    ----------
    For details on each of these different parameters, please read directly the
    metadatas specified in each of this dataclass fields.

    min_dist : `float`

    hydroph_dist_max : `float`

    hbond_dist_max : `float`

    hbond_don_angle_min : `float`

    pistack_dist_max : `float`

    pistack_ang_dev : `float`

    pistack_offset_max : `float`

    pication_dist_max : `float`

    saltbridge_dist_max : `float`

    halogen_dist_max : `float`

    halogen_acc_angle : `float`

    halogen_don_angle : `float`

    halogen_angle_dev : `float`

    water_bridge_dist_min : `float`

    water_bridge_dist_max : `float`

    water_bridge_omega_min : `float`

    water_bridge_omega_max : `float`

    water_bridge_theta_min : `float`

    metal_dist_max : `float`
    """

    def __post_init__(self) -> None:
        check_typing(self)

    # =======
    # GENERAL
    # =======
    min_dist: float = dataclasses.field(
        default=0.5,
        metadata={
            "description": "Minimum distance for all distance thresholds."
        },
    )

    # Some distance thresholds were extended (max. 1.0A) if too restrictive too
    # account for low-quality structures.

    # ===============
    # HYDRHOPHOBICITY
    # ===============
    hydroph_dist_max: float = dataclasses.field(
        default=4.0,
        metadata={
            "description": (
                "Distance cutoff for detection of hydrophobic contacts."
            )
        },
    )

    # ======
    # H-BOND
    # ======
    hbond_dist_max: float = dataclasses.field(
        default=2.5,
        metadata={
            "description": (
                "Maximum distance between hydrogen bond donor and acceptor."
            ),
            "paper": "Hubbard & Haider, 2001",
        },
    )
    hbond_don_angle_min: float = dataclasses.field(
        default=120.0,
        metadata={
            "description": (
                "Minimum angle at the hydrogen bond donor (in degrees)."
            ),
            "paper": "Hubbard & Haider, 2001",
        },
    )
    hbond_don_angle_max: float = dataclasses.field(
        default=180.0,
        metadata={
            "description": (
                "Maximum angle at the hydrogen bond donor (in degrees)."
            ),
            "paper": "Hubbard & Haider, 2001",
        },
    )

    # ===========
    # PI STACKING
    # ===========
    pistack_dist_max: float = dataclasses.field(
        default=5.5,
        metadata={
            "description": (
                "Maximum distance for parallel or offset pistacking."
            ),
            "paper": "McGaughey, 1998",
        },
    )
    pistack_ang_dev: float = dataclasses.field(
        default=30.0,
        metadata={
            "description": (
                "Maximum Deviation from parallel or perpendicular orientation "
                "(in degrees)."
            ),
        },
    )
    pistack_offset_max: float = dataclasses.field(
        default=2.0,
        metadata={
            "description": "Maximum offset of the two rings (benzene radius).",
            "shift": "0.5 A",
        },
    )

    # =========
    # PI CATION
    # =========
    pication_dist_max: float = dataclasses.field(
        default=6.0,
        metadata={
            "description": (
                "Maximum distance between charged + atom and aromatic ring "
                "centre."
            ),
            "paper": "Gallivan and Dougherty, 1999",
        },
    )

    # =========
    # PI ANION
    # =========
    pianion_dist_max: float = dataclasses.field(
        default=5.0,
        metadata={
            "description": (
                "Maximum distance between charged - atom and aromatic ring "
                "centre."
            ),
            "paper": (
                "Lucas X, Bauzá A, Frontera A, Quiñonero D. A thorough "
                "anion-pi interaction study in biomolecules: on the "
                "importance of cooperativity effects. Chem Sci. 2016 Feb "
                "1;7(2):1038-1050"
            ),
        },
    )

    # ===========
    # SALT BRIDGE
    # ===========
    saltbridge_dist_max: float = dataclasses.field(
        default=5.5,
        metadata={
            "description": (
                "Maximum distance between centres of charge for salt bridges."
            ),
            "paper": "Barlow and Thornton, 1983",
            "shift": "+ 1.5 A",
        },
    )

    # =======
    # HALOGEN
    # =======
    halogen_dist_max: float = dataclasses.field(
        default=4.0,
        metadata={
            "description": "Maximum distance between oxygen and halogen.",
            "paper": "Halogen bonds in biological molecules, Auffinger",
            "shift": "+ 0.5 A",
        },
    )
    halogen_acc_angle: float = dataclasses.field(
        default=120.0,
        metadata={
            "description": "Optimal acceptor angle.",
            "paper": "Halogen bonds in biological molecules, Auffinger",
        },
    )
    halogen_don_angle: float = dataclasses.field(
        default=165.0,
        metadata={
            "description": "Optimal donor angle.",
            "paper": "Halogen bonds in biological molecules, Auffinger",
        },
    )
    halogen_angle_dev: float = dataclasses.field(
        default=30.0,
        metadata={
            "description": (
                "Maximum deviation from optimal angle (in degrees)."
            ),
        },
    )

    # ============
    # WATER BRIDGE
    # ============
    water_bridge_dist_min: float = dataclasses.field(
        default=2.5,
        metadata={
            "description": (
                "Minimum distance between water oxygen and polar atom."
            ),
            "paper": "Jiang et al., 2005",
            "shift": "- 0.1 A",
        },
    )
    water_bridge_dist_max: float = dataclasses.field(
        default=4.1,
        metadata={
            "description": (
                "Maximum distance between water oxygen and polar atom."
            ),
            "paper": "Jiang et al., 2005",
            "shift": "+ 0.5 A",
        },
    )
    water_bridge_omega_min: float = dataclasses.field(
        default=71.0,
        metadata={
            "description": (
                "Minimum angle between acceptor, water oxygen and donor "
                "hydrogen."
            ),
            "paper": "Jiang et al., 2005",
            "shift": "- 9 degrees",
        },
    )
    water_bridge_omega_max: float = dataclasses.field(
        default=140.0,
        metadata={
            "description": (
                "Maximum angle between acceptor, water oxygen and donor "
                "hydrogen."
            ),
            "paper": "Jiang et al., 2005",
        },
    )
    water_bridge_theta_min: float = dataclasses.field(
        default=100.0,
        metadata={
            "description": (
                "Minimum angle between water oxygen, donor hydrogen and donor "
                "atom (in degrees)."
            ),
            "paper": "Jiang et al., 2005",
        },
    )

    # =====
    # METAL
    # =====
    metal_dist_max: float = dataclasses.field(
        default=3.0,
        metadata={
            "description": (
                "Maximum distance between metal ion and interacting atom."
            ),
            "paper": "Harding, 2001",
        },
    )


class ComputeViridis:
    """Callable color generator based on the Matplotlib *viridis* colormap.

    Notes
    -----
    The generator can be called at most `length` times. Any additional call
    raises an error.
    """

    def __init__(self, length: int) -> None:
        """Initialize the viridis color generator.

        Parameters
        ----------
        length : `int`
            The total number of colors to generate.
        """
        self.length: int = length - 1
        self.index: int = 0

    def __call__(self) -> str:
        """Return the next color in hexadecimal format.

        Returns
        -------
        `str`
            A hexadecimal color string.

        Raises
        ------
        `ReferenceError`
            If the generator is called more times than allowed.
        """
        if self.index > self.length:
            Error.OVER_CALL(ReferenceError, class_name=self.__class__.__name__)

        viridis: matplotlib.colors.ListedColormap = pyplot.cm.viridis
        color_value: str = matplotlib.colors.to_hex(
            viridis(self.index / self.length)
        )

        self.index += 1

        return color_value


type DrawingConfiguration = DataClass


def create_drawning_configuration() -> DrawingConfiguration:
    """Create the drawing configuration dataclass for molecular visualization.

    Notes
    -----
    Field names use a double underscore (`__`) separator to allow correct
    extraction by the visualization backend `strange.visualization.molstar`.

    Returns
    -------
    `DrawingConfiguration`
        A dynamically created dataclass containing drawing parameters.
    """
    NAME: str = "DrawningConfiguration"

    SPHERE_RADIUS: float = 1 / 3
    LINE_RADIUS: float = 1 / 30
    DASH_LENGTH: float = 1 / 30

    field_setup: list[tuple] = []
    viridis: ComputeViridis = ComputeViridis(len(PharmacophoreFamily))

    # ====
    # NOTE
    # ====
    # The double "__" in next field are very important. It is to extract the
    # good field when generating the visualization in
    # `strange.visualization.molstar`.

    for pharmacophore in PharmacophoreFamily:
        field_setup += [(f"{pharmacophore}__color", str, viridis())]
        field_setup += [
            (f"{pharmacophore}__radius", float | int, SPHERE_RADIUS)
        ]

    viridis: ComputeViridis = ComputeViridis(len(InteractionType))

    for interaction in InteractionType:
        field_setup += [(f"{interaction}__color", str, viridis())]
        field_setup += [(f"{interaction}__radius", float | int, LINE_RADIUS)]
        field_setup += [
            (f"{interaction}__dash_length", float | int, DASH_LENGTH)
        ]

    def __post_init__(self):
        check_typing(self)

    return dataclasses.make_dataclass(
        NAME, field_setup, namespace={"__post_init__": __post_init__}
    )


type Miscellaneous = DataClass


def create_miscellaneous_configuration() -> Miscellaneous:
    """Create the miscellaneous configuration dataclass.

    Returns
    -------
    `Miscellaneous`
        A dynamically created dataclass containing miscellaneous settings.
    """
    NAME: str = "Miscellaneous"

    field_setup: list[tuple] = [
        ("csv_separator", str, ","),
        ("enable_vdw_radius", bool, False),
        ("number_neighbour", int, 3),
    ]

    for pharmacophore in PharmacophoreFamily:
        field_setup += [(f"enable_{pharmacophore}", bool, True)]

    for interaction in InteractionType:
        field_setup += [(f"enable_{interaction}", bool, True)]

    def __post_init__(self):
        check_typing(self)

    return dataclasses.make_dataclass(
        NAME, field_setup, namespace={"__post_init__": __post_init__}
    )
