"""Classic and parametric NACA 4/5-digit thickness relations."""

import numpy as np

from buffalo_wings.internal.numeric import as_float_array
from buffalo_wings.type_aliases import FloatArray, FloatInput

from .thickness_base import (
    MAX_THICKNESS_INDEX_LIMIT,
    OPEN_TRAILING_EDGE_THICKNESS,
    Thickness,
)


class Naca45DigitThicknessClassic(Thickness):
    """
    Class for the classic NACA 4-digit and 5-digit airfoil thickness.

    The thickness is parameterized on the square root of the chord location
    where the thickness is desired. This is to remove singularities that
    occur at the leading edge for the typical chord length parameterization.

    Notes
    -----
    To obtain a classic thickness profile the maximum thickness should be set
    to an integer value, i.e. 12. However, any floating point value can be
    passed in, i.e. 12.3, if a more accurate maximum thickness is needed to
    be specified.
    """

    def __init__(self, mti: float) -> None:
        """
        Initialize a classic NACA thickness distribution.

        Parameters
        ----------
        mti : float
            Maximum-thickness index as a percent of chord.

        Raises
        ------
        ValueError
            If ``mti`` is outside the supported classic NACA range.
        """
        self._a = (
            np.array([0.29690, -0.12600, -0.35160, 0.28430, -0.10150]) / 0.20
        )
        self.max_thickness_index = mti

    @property
    def max_thickness_index(self) -> float:
        """
        Return the maximum-thickness index.

        Returns
        -------
        float
            Maximum thickness as a percent-chord index.
        """
        return 100 * self._tmax

    @max_thickness_index.setter
    def max_thickness_index(self, mti: float) -> None:
        if mti < 0 or mti >= MAX_THICKNESS_INDEX_LIMIT:
            raise ValueError(f"Invalid NACA 4/5-digit maximum thickness: {mti}")

        self._tmax = mti / 100.0

    @property
    def a(self) -> FloatArray:
        """
        Return the polynomial coefficients.

        Returns
        -------
        FloatArray
            Coefficients for the classic thickness relation.
        """
        return self._a

    def discontinuities(self) -> list[float]:  # noqa: PLR6301
        """
        Return the locations of any discontinuities in the thickness.

        Returns
        -------
        List[float]
            Parametric coordinates of any discontinuities.
        """
        return []

    def max_thickness_parameter(self) -> float:  # noqa: PLR6301
        """
        Return parameter coordinate of maximum thickness.

        Returns
        -------
        float
            Parameter coordinate of maximum thickness.
        """
        return np.sqrt(0.3)

    def delta(self, t: FloatInput) -> FloatArray:
        """
        Return the thickness at specified parameter location.

        Parameters
        ----------
        t : numpy.ndarray
            Parameter location of interest. Equal to the square root of the
            desired chord location.

        Returns
        -------
        numpy.ndarray
            Thickness at specified parameter.
        """
        t = as_float_array(t)
        t2 = t**2
        return (
            self._tmax
            * t
            * (
                self.a[0]
                + t
                * (
                    self.a[1]
                    + t2 * (self.a[2] + t2 * (self.a[3] + t2 * self.a[4]))
                )
            )
        )

    def delta_t(self, t: FloatInput) -> FloatArray:
        """
        Return first derivative of thickness at specified parameter location.

        Parameters
        ----------
        t : numpy.ndarray
            Parameter location of interest. Equal to the square root of the
            desired chord location.

        Returns
        -------
        numpy.ndarray
            First derivative of thickness at specified parameter.
        """
        t = as_float_array(t)
        t2 = t**2
        return self._tmax * (
            self.a[0]
            + 2
            * t
            * (
                self.a[1]
                + t2
                * (2 * self.a[2] + t2 * (3 * self.a[3] + 4 * t2 * self.a[4]))
            )
        )

    def delta_tt(self, t: FloatInput) -> FloatArray:
        """
        Return second derivative of thickness at specified parameter location.

        Parameters
        ----------
        t : numpy.ndarray
            Parameter location of interest. Equal to the square root of the
            desired chord location.

        Returns
        -------
        numpy.ndarray
            Second derivative of thickness at specified parameter.
        """
        t = as_float_array(t)
        t2 = t**2
        return (
            2
            * self._tmax
            * (
                self.a[1]
                + t2
                * (6 * self.a[2] + t2 * (15 * self.a[3] + 28 * self.a[4] * t2))
            )
        )


class Naca45DigitThicknessParams(Naca45DigitThicknessClassic):
    """
    Parametric NACA 4-digit and 5-digit airfoil thickness.

    The thickness is parameterized on the square root of the chord location
    where the thickness is desired. This is to remove singularities that
    occur at the leading edge for the typical chord length parameterization.

    This class extends the standard thickness distribution relation by
    solving for coefficients from the original constraints even for
    non-integer parameters, allowing a closed trailing edge, and allowing
    the leading-edge radius condition to be enforced directly.

    Notes
    -----
    Specifying the same parameters as the classic thickness profile will not
    result in an identical thickness profile because the canonical
    coefficients do not match the stated constraints in the original source
    from Jacobs, Ward, and Pinkerton (1933).
    """

    def __init__(self, mti: float, closed_te: bool, use_radius: bool) -> None:
        """
        Initialize the parametric NACA thickness distribution.

        Parameters
        ----------
        mti : float
            Maximum-thickness index as a percent of chord.
        closed_te : bool
            Whether the trailing edge closes to zero thickness.
        use_radius : bool
            Whether to enforce the classic leading-edge radius condition.
        """
        super().__init__(mti=mti)
        self._closed_te = closed_te
        self._use_radius = use_radius
        self._calculate_a()

    @property
    def closed_trailing_edge(self) -> bool:
        """
        Return the trailing-edge closure flag.

        Returns
        -------
        bool
            ``True`` when the trailing edge closes to zero thickness.
        """
        return self._closed_te

    @closed_trailing_edge.setter
    def closed_trailing_edge(self, closed_te: bool) -> None:
        self._closed_te = closed_te
        self._calculate_a()

    @property
    def use_leading_edge_radius(self) -> bool:
        """
        Return the leading-edge treatment flag.

        Returns
        -------
        bool
            ``True`` when the classic leading-edge radius is enforced.
        """
        return self._use_radius

    @use_leading_edge_radius.setter
    def use_leading_edge_radius(self, use_radius: bool) -> None:
        self._use_radius = use_radius
        self._calculate_a()

    def _calculate_a(self) -> None:
        """
        Recompute the classic-thickness polynomial coefficients.

        Returns
        -------
        None
            This method updates the stored coefficient array in place.
        """
        # solve for new values of the coefficients using chord based
        # parameterization
        b_matrix = np.zeros([5, 5])
        r = np.zeros([5, 1])

        # first row is leading edge condition
        i = 0
        if self._use_radius:
            b_matrix[i, :] = [1, 0, 0, 0, 0]
            r[i] = 0.29690
        else:
            xi_1c = 0.1
            t_1c = 0.078
            b_matrix[i, :] = [
                np.sqrt(xi_1c),
                xi_1c,
                xi_1c**2,
                xi_1c**3,
                xi_1c**4,
            ]
            r[i] = t_1c

        # second row is the maximum thickness at 0.3c
        i = 1
        xi_max = 0.3
        b_matrix[i, :] = [
            0.5 / np.sqrt(xi_max),
            1,
            2 * xi_max,
            3 * xi_max**2,
            4 * xi_max**3,
        ]
        r[i] = 0.0

        # third row is the maximum thickness of 0.2c
        i = 2
        t_max = 0.1
        b_matrix[i, :] = [
            np.sqrt(xi_max),
            xi_max,
            xi_max**2,
            xi_max**3,
            xi_max**4,
        ]
        r[i] = t_max

        # fourth row is trailing edge slope
        i = 3
        te_slope = -0.234
        b_matrix[i, :] = [0.5, 1, 2, 3, 4]
        r[i] = te_slope

        # fith row is trailing edge thickness
        i = 4
        t_te = 0.0 if self._closed_te else OPEN_TRAILING_EDGE_THICKNESS
        b_matrix[i, :] = [1, 1, 1, 1, 1]
        r[i] = t_te

        self._a = (np.linalg.solve(b_matrix, r).transpose()[0]) / 0.20
