import numbers
from typing import List, Optional, Union, Tuple

import numpy

from geogst.core.geometries.shapes.lines import *


class MultiLine(Shape):

    proper_space = 1

    @property
    def embedding_space(self) -> int:
        if not self._lines:
            return 0
        return self._lines[0].embedding_space

    def __init__(self,
                 lines_list: List[Ln]):

        super(MultiLine, self).__init__()

        self._lines = lines_list

    def lines(self) -> List[Ln]:

        return self._lines

    def line(self, ndx: numbers.Integral):

        return self._lines[ndx]

    def num_lines(self):

        return len(self.lines())

    def add(self, line: Ln):
        """Adds a line to the multiline."""

        self._lines.append(line)

    def clone(self):
        """Clones the multiline."""

        return MultiLine(self.lines())

    def num_points(self):

        num_points = 0
        for line in self.lines():
            num_points += line.num_points()

        return num_points

    def __repr__(self) -> str:
        """
        Represents a MultiLine instance as a shortened text.

        :return: a textual shortened representation of a MultiLine instance.
        """

        num_lines = self.num_lines()

        if num_lines == 0:
            return f"Empty MultiLine"

        num_points = self.num_points()

        return f"MultiLine made up by {num_lines} line(s) and {num_points} total point(s)"

    def is_continuous(self):

        for line_ndx in range(len(self._lines) - 1):
            if not self.lines()[line_ndx].pts()[-1].is_coincident(self.lines()[line_ndx + 1].pts()[0]) or \
               not self.lines()[line_ndx].pts()[-1].is_coincident(self.lines()[line_ndx + 1].pts()[-1]):
                return False

        return True

    def is_unidirectional(self):

        for line_ndx in range(len(self.lines()) - 1):
            if not self.lines()[line_ndx].pts()[-1].is_coincident(self.lines()[line_ndx + 1].pts()[0]):
                return False

        return True

    @property
    def x_min(self) -> Optional[float]:

        if self.num_points() == 0:
            return None
        else:
            return np.nanmin([line.x_min for line in self.lines()])[0]

    @property
    def x_max(self):

        if self.num_points() == 0:
            return None
        else:
            return np.nanmax([line.x_max for line in self.lines()])

    def x_range(self):

        return self.x_min, self.x_max

    @property
    def y_min(self):

        if self.num_points() == 0:
            return None
        else:
            return np.nanmin([line.y_min for line in self.lines()])

    @property
    def y_max(self):

        if self.num_points() == 0:
            return None
        else:
            return np.nanmax([line.y_max for line in self.lines()])

    def y_range(self):

        return self.y_min, self.y_max


    @property
    def z_min(self):

        if self.num_points() == 0:
            return None
        else:
            return np.nanmin([line.z_min for line in self.lines()])

    @property
    def z_max(self):

        if self.num_points() == 0:
            return None
        else:
            return np.nanmax([line.z_max for line in self.lines()])

    def __iter__(self):

        return (line for line in self.lines)

    def length(self) -> numbers.Real:
        """Returns length of the multiline."""

        return sum(line.length() for line in self)

    def bounding_box(self) -> Union[None, Tuple[numbers.Real, numbers.Real, numbers.Real, numbers.Real]]:

        try:

            return self.x_min, self.y_min, self.x_max, self.y_max

        except Exception as e:

            print(f"\n{e!r}")
            return None

    def area(self):
        """Calculate shape area"""

        return 0.0

    def to_line(self):

        return Ln.from_points(*[point for line in self.lines() for point in line.pts()])


MLine = Union[Ln, MultiLine]


def xytuple_l2_to_multiline2d(
        xytuple_list2
) -> MultiLine:

    # input is a list of list of (x,y) values

    lines_list = []
    for xy_list in xytuple_list2:
        lines_list.append(xytuple_list_to_line2d(xy_list))

    return MultiLine(lines_list)
