import math
import re
import numpy as np
from .helpers import channel_end, Circle
from .shape_generator import CrossSection
[docs]class CrossSectionHolding(CrossSection):
    """
    cross section class for Holding Graz
    Attributes:
        add_dim (bool): add the dimension (height x width) to the label and output filename
        add_dn (bool): add the channel diameter (DN ...) to the label and output filename
    """
[docs]    def __init__(self, label, description=None, add_dim=False, add_dn=None, **kwargs):
        """Initialise the cross-section class
        Args:
            label (str): name/label/number of the cross-section
            description (Optional[str]): optional description of the cross-section
            add_dim (bool): if the dimensions should be added to :py:attr:`~out_filename` used for the export
            add_dn (Optional[float]): if the channel dimension should be added to :py:attr:`~out_filename`
                                    used for the export enter the diameter as float
            **kwargs (object): see :py:attr:`~__init__`
        Keyword Args:
            description (Optional[str]): optional description of the cross section
            height (float): absolute height of the CS
            width (Optional[float]): absolute width of the CS (optional) can be calculated
            working_directory (str): directory where the files get saved
            unit (Optional[str]): enter unit to add the unit in the plots
        """
        if isinstance(label, float):
            label = f'{label:0.0f}'
        else:
            label = str(label)
        label = label.strip('Pr_')
        self.add_dim = add_dim
        self.add_dn = add_dn
        self.description = description
        CrossSection.__init__(self, label, **kwargs) 
    def __repr__(self):
        return f'CrossSectionHolding({self})'
    @property
    def identifier(self):
        s = 'Pr_' + self.label
        if self.add_dim:
            s += f'+{self.height:0.0f}'
            if self.width:
                s += f'x{self.width:0.0f}'
        if self.add_dn:
            s += f'+DN{self.add_dn:0.0f}'
        return s
    def name_string(self):
        return self.identifier.replace('+', ' | ')
    def title_string(self):
        s = CrossSection.title_string(self)
        if (self.description is not None) and (self.label != self.description):
            s += f': {str(self.description).strip()}'
        return s
    ####################################################################################################################
[docs]    @classmethod
    def standard(cls, label, description=None, height=np.NaN, width=None, r_channel=None, r_roof=None, r_wall=None,
                 slope_bench=None, r_round=None, r_wall_bottom=None, h_bench=None, pre_bench=None, w_channel=None,
                 **kwargs):
        """
        standard cross section
        Args:
            label (str): see :py:attr:`~__init__`
            description (str): see :py:attr:`~__init__`
            height (float): see :py:attr:`~__init__`
            width (float): see :py:attr:`~__init__`
            r_channel (float): radius of the dry-weather channel (=Trockenwetter Rinne)
            w_channel (float): half width of the channel, only in combination with ``r_channel`` active
            pre_bench (float): slope of the upper end of the channel in degree, only in combination with
            ``r_channel`` active
            r_round (float): radius of the rounding of the edges, only in combination with ``r_channel`` active
            h_bench (float): height where the bench begins, only in combination with ``r_channel`` active
            slope_bench (float): slope of the bench (=Berme) in degree, or slope of the rainwater-floor (
            =Regenwetterrinne)
            r_roof (float): radius of the roof (=Decke)
            r_wall (float): radius of the sidewall (=Seitenwand), only in combination with ``r_roof`` active
            r_wall_bottom (float): radius of the bottom sidewall (=untere Seitenwand), only in combination with
            ``r_wall`` active
            **kwargs (object): see :py:attr:`~__init__`
        Keyword Args:
            working_directory (str): directory where the files get saved
            unit (Optional[str]): enter unit to add the unit in the plots
        Returns:
            CrossSectionHolding: standard cross section
        Examples:
            see :doc:`standard_cross_section`
        .. figure:: images/standard.gif
            :align: center
            :alt: standard cross section
            :figclass: align-center
            Standard cross section
        +---------+---------------------+
        | english | deutsch             |
        +=========+=====================+
        | channel | Trockenwetter-Rinne |
        +---------+---------------------+
        | roof    | Firste/Decke        |
        +---------+---------------------+
        | wall    | Seitenwand          |
        +---------+---------------------+
        | bench   | Berme               |
        +---------+---------------------+
        """
        # ------------------------------------------------
        cross_section = cls(label=label, description=description, height=height, width=width, **kwargs)
        # ------------------------------------------------
        # TW-Rinne
        if r_channel is not None:
            cross_section.add(Circle(r_channel, x_m=r_channel))
            # ------------------------------------------------
            if pre_bench is not None:
                cross_section.add(channel_end(r_channel, pre_bench))
                if (h_bench is not None) or (slope_bench is None):
                    cross_section.add(pre_bench, '°slope')
                if h_bench is not None:
                    cross_section.add(h_bench)
            elif w_channel is not None:
                cross_section.add(None, w_channel)
            else:
                if h_bench is not None:
                    cross_section.add(h_bench)
                else:
                    cross_section.add(r_channel)
                    if r_round is None:
                        r_round = 0
                    cross_section.add(r_channel + r_round, r_channel)
        # ------------------------------------------------
        if slope_bench is not None:
            # Berme winkel in °
            cross_section.add(slope_bench, '°slope')
        # ------------------------------------------------
        if (r_channel is None) and (slope_bench is None):
            cross_section.add(0, width / 2)
        # ------------------------------------------------
        if r_roof is None:
            # eckige Decke
            cross_section.add(None, width / 2)
            cross_section.add(height, width / 2)
        else:
            if r_wall is None:
                cross_section.add(None, width / 2)
                cross_section.add(height - r_roof, width / 2)
            else:
                # ------------------------------------------------
                h1 = math.sqrt((r_wall - r_roof) ** 2 - (r_wall - width / 2) ** 2)
                # h_middle = round(height - r_roof - h1, 8)
                h_middle = height - r_roof - h1
                # ------------------------------------------------
                if r_wall_bottom is None:
                    cross_section.add(None, width / 2)
                    cross_section.add(h_middle, width / 2)
                else:
                    cross_section.add(Circle(r_wall_bottom, x_m=h_middle, y_m=width / 2 - r_wall_bottom))
                    cross_section.add(h_middle)
                # ------------------------------------------------
                cross_section.add(Circle(r_wall, x_m=h_middle, y_m=width / 2 - r_wall))
                cross_section.add(h_middle + h1 / (r_wall - r_roof) * r_wall)
            # ------------------------------------------------
            cross_section.add(Circle(r_roof, x_m=height - r_roof))
        # ------------------------------------------------
        return cross_section 
    ####################################################################################################################
[docs]    @classmethod
    def box(cls, label, height, width, channel=None, bench=None, roof=None, rounding=0.0, **kwargs):
        """
        pre defined box (=Kasten) cross section
        Args:
            label (str): see :py:attr:`~__init__`
            height (float): see :py:attr:`~__init__`
            width (float): see :py:attr:`~__init__`
            channel (Optional[float]): diameter of the dry weather channel
            bench (Optional[float]): bench (=Berme)
                - ``''``: flache Berme
                - ``'R'``: V-förmiges Profil
                - ``'H'``: Schräge Verschneidung
            roof (Optional[float]): roof (=Decke)
                - ``''``: gerade
                - ``'B'``: Bogen
                - ``'K'``: Kreis
            rounding (Optional[float]): rounding of the edges
            **kwargs (object): see :py:attr:`~__init__`
        Keyword Args:
            description (Optional[str]): optional longer name of the cross section
            working_directory (str): directory where the files get saved
            unit (Optional[str]): enter unit to add the unit in the plots
        Returns:
            CrossSectionHolding: pre defined box (=Kasten) cross section
        Examples:
            see :doc:`show_case-kasten`
        """
        name = 'K'
        cross_section = cls(label=label, height=height, width=width, **kwargs)
        # ------------------------------------------------
        if channel is not None:
            channel = float(channel)
        if bench is None:
            bench = ''
        if roof is None:
            roof = ''
        # ------------------------------------------------
        if channel or bench:
            name += '.'
            bench = str(bench).strip()
            if isinstance(channel, float):
                name += f'{channel:0.0f}'
                # diameter to radius
                channel /= 2
                cross_section.add(Circle(channel, x_m=channel))
            if isinstance(bench, str):
                # '' | 'R' | 'H'
                if bench != '45':
                    name += str(bench)
                if bench == 'R':
                    cross_section.add(30, '%slope')
                    cross_section.add(None, width / 2)
                elif bench == 'H':
                    cross_section.add(channel_end(channel, 45))
                    cross_section.add(45, '°slope')
                    cross_section.add(None, width / 2)
                elif bench == '45':
                    cross_section.add(channel_end(channel, 45))
                    cross_section.add(45, '°slope')
                    cross_section.add(channel + rounding)
                    cross_section.add(channel + rounding, width / 2)
                else:
                    # Berme
                    # cross_section.add(channel, width / 2)
                    # cross_section.add(channel + rounding, width / 2)
                    # --------either this
                    cross_section.add(channel_end(channel, 45))
                    cross_section.add(45, '°slope')
                    # --------or this
                    # cross_section.add(channel, channel)
                    # --------
                    cross_section.add(channel + rounding, None)
                    cross_section.add(5, '°slope')
                    cross_section.add(None, width / 2)
        else:
            # ebene Sohle
            cross_section.add(0, width / 2)
        # ------------------------------------------------
        if roof:
            # '' | 'B' | 'K'
            name += '_' + str(roof)
            if roof == '':
                # gerade Decke
                cross_section.add(height, width / 2)
            elif roof == 'B':
                # Bogen-Decke
                cross_section.add(height - width * (1 - math.cos(math.radians(30))), width / 2)
                cross_section.add(Circle(width, x_m=height - width))
            elif roof == 'K':
                # Kreis Decke
                cross_section.add(height - width / 2, width / 2)
                cross_section.add(Circle(width / 2, x_m=height - width / 2))
        else:
            # gerade Decke
            cross_section.add(height, width / 2)
        # ------------------------------------------------
        if cross_section.label is None or cross_section.label == '':
            cross_section.label = name
        return cross_section 
    ####################################################################################################################
[docs]    @classmethod
    def box_from_string(cls, label, **kwargs):
        """
        create pre defined box (=Kasten) cross section with the string label.
        This function takes the information from the label and pass them to the :py:attr:`~box` - function.
        Args:
            label (str): see the :doc:`show_case-kasten`
            **kwargs (object): see :py:attr:`~__init__`
        Keyword Args:
            height (float): see :py:attr:`~__init__`
            width (float): see :py:attr:`~__init__`
            rounding (Optional[float]): rounding of the edges
            description (Optional[str]): optional longer name of the cross section
            working_directory (str): directory where the files get saved
            unit (Optional[str]): enter unit to add the unit in the plots
        Returns:
            CrossSectionHolding: pre defined box (=Kasten) cross section
        Examples:
            see :doc:`show_case-kasten`
            .. figure:: images/Kasten-Profile.gif
                :align: center
                :alt: Kasten-Profile
                :figclass: align-center
                Kasten-Profile
        """
        infos = re.findall(r'(K)(\.?)(\d*)([RH]?)_?([BK]?)', label)  # _(\d+)x(\d+)
        # 'Pr_K.30K' == 'Pr_K.30_K'
        if len(infos) == 1:
            infos = infos[0]
            _, _, channel, bench, roof = infos
            if channel != '':
                channel = float(channel)
            else:
                channel = None
            bench, roof = [x_ if x_ != '' else None for x_ in (bench, roof)]
            cross_section = cls.box(label, channel=channel, bench=bench, roof=roof, **kwargs)
            return cross_section
        # --------------------------------------
        else:
            raise NotImplementedError(f'"{label}" unknown !') 
[docs]    @classmethod
    def from_point_cloud(cls, relative_coordinates, *args, **kwargs):
        """
        get the cross sections from a point cloud where every point is relative to the lowers point in the
        cross section
        Args:
            relative_coordinates (list[tuple[float, float]] | numpy.array): list of height- and width tuple
                                                  with the origin in the lowest point of the cross section
            *args: arguments, see :py:attr:`~__init__`
            **kwargs: keyword arguments, see :py:attr:`~__init__`
        Keyword Args:
            label (str): main name/label/number of the cross section
            description (Optional[str]): optional longer name of the cross section
            height (float): absolute height of the CS
            width (Optional[float]): absolute width of the CS (optional) can be calculated
            working_directory (str): directory where the files get saved
            unit (Optional[str]): enter unit to add the unit in the plots
            add_dim (bool): if the dimensions should be added to :py:attr:`~out_filename` used for the export
            add_dn (Optional[float]): if the channel dimension should be added to :py:attr:`~out_filename`
                                    used for the export enter the diameter as float
        Returns:
            CrossSectionHolding: of the point cloud
        .. figure:: images/point_cloud.gif
            :align: center
            :alt: point cloud
            :figclass: align-center
            Point cloud
        """
        if isinstance(relative_coordinates, (list, tuple)):
            x, y = zip(*relative_coordinates)
            x = np.array(x)
            y = np.array(y)
        elif isinstance(relative_coordinates, np.ndarray):
            x = relative_coordinates[:, 0]
            y = relative_coordinates[:, 1]
        else:
            raise NotImplementedError()
        height = max(y)
        width = max(x) - min(x)
        kwargs.update({'height': height, 'width': width})
        cross_section = cls(*args, **kwargs)
        # for the interpolation
        y = np.append(y, [0])
        x = np.append(x, [0])
        yi = sorted(set(y))
        xi = np.interp(yi, y[:np.argmax(y) + 1], x[:np.argmax(y) + 1]) - \
             
np.interp(yi, y[::-1][:np.argmax(y[::-1]) + 1], x[::-1][:np.argmax(y[::-1]) + 1])
        xi /= 2
        for x, y in zip(yi, xi):
            if y == 0 and x in (0, height):
                continue
            cross_section.add(x, y)
        return cross_section