import math
from typing import Any, Generator, Iterable, Iterator, List, Optional

import axipy

from ._progress_with_step import _ProgressWithStepMixIn


class _MIFilesResultBoundsCalculator(_ProgressWithStepMixIn):

    class BoundsListEmpty(Exception):
        def __init__(self) -> None:
            super().__init__("Список охватов входных файлов пустой.")

    def __init__(
        self,
        inp_feature_iters: Iterable[Iterator[axipy.Feature]],
        inp_cs: Optional[axipy.CoordSystem] = None,
        out_cs: Optional[axipy.CoordSystem] = None,
    ) -> None:
        self.__inp_feature_iters: Iterable[Iterator[axipy.Feature]] = inp_feature_iters
        self.__inp_cs: Optional[axipy.CoordSystem] = inp_cs
        self.__out_cs: Optional[axipy.CoordSystem] = out_cs

        self.__task: Optional[axipy.Task] = None

    def calculate(self, task: Optional[axipy.Task] = None) -> axipy.Rect:
        self.__task = task

        r: axipy.Rect = self.__calculate_result_bounds()
        x_min, y_min, x_max, y_max = r.xmin, r.ymin, r.xmax, r.ymax
        width, height = r.width, r.height
        # Start
        max_length = max(width, height)
        if max_length == 0:
            max_length = max(abs(x_max), abs(y_max))
            if max_length == 0:
                max_length = 1e8

        p = math.ceil(math.log10(abs(max_length)))
        if abs(p) >= 20:
            raise Exception("Слишком большое значение охвата")

        if self.__in_extent(r, -(10**p), 10**p):
            x_min, y_min, x_max, y_max = (-(10**p), -(10**p), 10**p, 10**p)
        else:
            if self.__in_extent(r, 0, 2 * 10**p):
                x_min, y_min, x_max, y_max = (0, 0, 2 * 10**p, 2 * 10**p)
            else:
                if self.__in_extent(r, -2 * 10**p, 0):
                    x_min, y_min, x_max, y_max = (-2 * 10**p, -2 * 10**p, 0, 0)
                else:
                    c_x = self.__circle_center(x_min, x_max, p)
                    c_y = self.__circle_center(y_min, y_max, p)

                    x_min, y_min, x_max, y_max = (c_x - 10**p, c_y - 10**p, c_x + 10**p, c_y + 10**p)

        return axipy.Rect(x_min, y_min, x_max, y_max)

    def __generate_bounds_from_feature_iter(
        self, feature_iter: Iterator[axipy.Feature]
    ) -> Generator[axipy.Rect, Any, None]:
        items = feature_iter

        for item in items:
            if self.__task:
                self.__task.raise_if_canceled()

            g = item.geometry
            if g is None:
                continue

            if self.__inp_cs is not None:
                g.coordsystem = self.__inp_cs

            if self.__out_cs is not None:
                g = g.reproject(self.__out_cs)

            bounds = g.bounds
            yield bounds

    def __generate_bounds_from_feature_iters(self) -> Generator[axipy.Rect, Any, None]:
        iters = list(self.__inp_feature_iters)
        if self.__task:
            self.init_progress_with_step(self.__task, len(iters))

        for inp_feature_iter in iters:
            self.add_value_progress_with_step()
            bounds = self.__get_bounds_from_feature_iter(inp_feature_iter)
            if bounds is not None:
                yield bounds

    def __merge_result_rect(self, bounds_list: List[axipy.Rect]) -> axipy.Rect:
        if len(bounds_list) < 1:
            raise self.BoundsListEmpty

        result_rect = bounds_list[0]
        for bounds in bounds_list[1:]:
            result_rect = result_rect.merge(bounds)

        return result_rect

    def __calculate_result_bounds(self) -> axipy.Rect:
        bounds_list = list(self.__generate_bounds_from_feature_iters())
        if len(bounds_list) < 1:
            raise self.BoundsListEmpty
        return self.__merge_result_rect(bounds_list)

    def __get_bounds_from_feature_iter(self, feature_iter: Iterator[axipy.Feature]) -> axipy.Rect:
        bounds_list = list(self.__generate_bounds_from_feature_iter(feature_iter))
        return self.__merge_result_rect(bounds_list)

    def __in_extent(self, r: axipy.Rect, extent1: int, extent2: int) -> bool:
        """
        Функция проверяет нахождение точек во входном промежутке, по x и по y.
        """
        return extent1 < r.xmin and r.xmax < extent2 and extent1 < r.ymin and r.ymax < extent2

    def __circle_center(self, arg_1: float, arg_2: float, p: int) -> int:
        """
        Функция поиска круглой середины.
        """
        return round((arg_1 + arg_2) / 2 / 10**p) * 10**p
