"""NACA 4-digit airfoil camber relations."""

import numpy as np

from buffalo_wings.type_aliases import FloatArray, FloatScalar

from .camber_base import CLASSIC_NACA_DIGIT_MAX, Camber


class Naca4DigitCamber(Camber):
    """Camber for the NACA 4-digit airfoils."""

    def __init__(self, mci: float, lci: float) -> None:
        """
        Initialize a NACA 4-digit camber line from its designation.

        Parameters
        ----------
        mci : float
            Maximum-camber index from the NACA designation.
        lci : float
            Maximum-camber-location index from the NACA designation.

        Raises
        ------
        ValueError
            If either designation index is outside the supported range.
        """
        # bootstrap values
        self._m: float = 2
        self._p: float = 2
        self.max_camber_index = mci
        self.loc_max_camber_index = lci

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

        Returns
        -------
        float
            Maximum camber as a designation index.
        """
        return 100.0 * self._m

    @max_camber_index.setter
    def max_camber_index(self, mci: float) -> None:
        """
        Set the maximum-camber index from the NACA designation.

        Parameters
        ----------
        mci : float
            Maximum-camber index from the NACA designation.

        Raises
        ------
        ValueError
            If ``mci`` is outside the supported classic 4-digit range.
        """
        if mci == 0:
            self._m = 0.0
            self._p = 0.0
        elif (mci < 0) or (mci > CLASSIC_NACA_DIGIT_MAX):
            raise ValueError(f"Invalid NACA 4-digit maximum camber: {mci}.")
        else:
            # check to see if currently no camber and force valid value
            if self._p == 0:
                self._p = 0.2

            self._m = mci / 100.0

    @property
    def loc_max_camber_index(self) -> float:
        """
        Return the maximum-camber-location index.

        Returns
        -------
        float
            Chordwise location of maximum camber as a designation index.
        """
        return 10.0 * self._p

    @loc_max_camber_index.setter
    def loc_max_camber_index(self, lci: float) -> None:
        """
        Set the max-camber location index from the NACA designation.

        Parameters
        ----------
        lci : float
            Chordwise location of maximum camber as a designation index.

        Raises
        ------
        ValueError
            If ``lci`` is outside the supported classic 4-digit range.
        """
        if lci == 0:
            self._m = 0.0
            self._p = 0.0
        elif (lci < 0) or (lci > CLASSIC_NACA_DIGIT_MAX):
            raise ValueError(
                f"Invalid NACA 4-digit maximum camber location: {lci}."
            )
        else:
            # check to see if currently no camber and force valid value
            if self._m == 0:
                self._m = 0.2

            self._p = lci / 10.0

    def joints(self) -> list[float]:
        """
        Return the locations of any joints/discontinuities in the camber line.

        Returns
        -------
        List[float]
            Xi-coordinates of any discontinuities.
        """
        if (self._m == 0) or (self._p == 0):
            return [0.0, 1.0]

        return [0.0, self._p, 1.0]

    def max_camber_parameter(self) -> FloatScalar:
        """
        Return parameter where the camber is maximum.

        Returns
        -------
        float
            Parameter where camber is maximum.
        """
        return self._m

    def _y(self, t: FloatArray) -> FloatArray:
        """
        Return the camber location at specified chord location.

        Parameters
        ----------
        t : numpy.ndarray
            Chord location of interest.

        Returns
        -------
        numpy.ndarray
            Camber at specified point.
        """
        if (self._m == 0) or (self._p == 0):
            return np.zeros_like(t)

        t = np.asarray(t, dtype=np.float64)

        def fore(t: FloatArray) -> FloatArray:
            return (self._m / self._p**2) * (2 * self._p * t - t**2)

        def aft(t: FloatArray) -> FloatArray:
            return (self._m / (1 - self._p) ** 2) * (
                1 + 2 * self._p * (t - 1) - t**2
            )

        return np.piecewise(t, [t <= self._p, t > self._p], [fore, aft])

    def _y_t(self, t: FloatArray) -> FloatArray:
        """
        Return first derivative of camber at specified chord location.

        Parameters
        ----------
        t : numpy.ndarray
            Chord location of interest.

        Returns
        -------
        numpy.ndarray
            First derivative of camber at specified point.
        """
        if (self._m == 0) or (self._p == 0):
            return np.zeros_like(t)

        t = np.asarray(t, dtype=np.float64)

        def fore(t: FloatArray) -> FloatArray:
            return 2 * (self._m / self._p**2) * (self._p - t)

        def aft(t: FloatArray) -> FloatArray:
            return 2 * (self._m / (1 - self._p) ** 2) * (self._p - t)

        return np.piecewise(t, [t <= self._p, t > self._p], [fore, aft])

    def _y_tt(self, t: FloatArray) -> FloatArray:
        """
        Return second derivative of camber at specified chord location.

        Parameters
        ----------
        t : numpy.ndarray
            Chord location of interest.

        Returns
        -------
        numpy.ndarray
            Second derivative of camber at specified point.
        """
        if (self._m == 0) or (self._p == 0):
            return np.zeros_like(t)

        t = np.asarray(t, dtype=np.float64)

        def fore(t: FloatArray) -> FloatArray:
            return -2 * (self._m / self._p**2) * np.ones_like(t)

        def aft(t: FloatArray) -> FloatArray:
            return -2 * (self._m / (1 - self._p) ** 2) * np.ones_like(t)

        return np.piecewise(t, [t <= self._p, t > self._p], [fore, aft])

    def _y_ttt(self, t: FloatArray) -> FloatArray:
        """
        Return third derivative of camber at specified chord location.

        Parameters
        ----------
        t : numpy.ndarray
            Chord location of interest.

        Returns
        -------
        numpy.ndarray
            Third derivative of camber at specified point.
        """
        if (self._m == 0) or (self._p == 0):
            return np.zeros_like(t)

        t = np.asarray(t, dtype=np.float64)

        def fore(t: FloatArray) -> FloatArray:
            return np.zeros_like(t)

        def aft(t: FloatArray) -> FloatArray:
            return np.zeros_like(t)

        return np.piecewise(t, [t <= self._p, t > self._p], [fore, aft])
