from typing import List

import axipy
from axipy import MultiPoint, Notifications, Pnt
from axipy.concurrent import (
    AxipyProgressHandler,
    ProgressGuiFlags,
    ProgressSpecification,
    task_manager,
)
from axipy.da import (
    Feature,
    Geometry,
    GeometryCollection,
    Line,
    LineString,
    MultiLineString,
    MultiPolygon,
    Polygon,
    data_manager,
)

from .. import helper

supported_types = (Line, LineString, Polygon, GeometryCollection)
unsupported_types = (MultiPoint,)

supported_types_string = ("Линия", "Полилиния", "Полигон", "Коллекция")


def revert(geom: Geometry) -> Geometry:
    if isinstance(geom, GeometryCollection):
        return revert_geometry_collection(geom)
    else:
        return revert_single_geom(geom)


def revert_geometry_collection(collection: GeometryCollection) -> GeometryCollection:
    if isinstance(collection, MultiPolygon):
        reverted_collection = MultiPolygon(collection.coordsystem)
    elif isinstance(collection, MultiLineString):
        reverted_collection = MultiLineString(collection.coordsystem)
    else:
        reverted_collection = GeometryCollection(collection.coordsystem)

    for geometry in collection:
        reverted_geometry = revert_single_geom(geometry)
        if reverted_geometry is not None:
            reverted_collection.append(reverted_geometry)
        else:
            """
            Если не можем поменять направление линии для какого-нибудь элемента
            коллекции просто добавляем его. Т.к. в итоге мы должны иметь ту же
            самую коллекцию только с изменёнными направлениями линий
            """
            reverted_collection.append(geometry)

    return reverted_collection


def revert_single_geom(geom: Geometry) -> Geometry:
    def to_reverse_list(pnts: List[Pnt]) -> List[Pnt]:
        return [p for p in reversed(pnts)]

    if isinstance(geom, Line):
        return Line(geom.end, geom.begin, cs=geom.coordsystem)
    elif isinstance(geom, LineString):
        return LineString(to_reverse_list(geom.points), cs=geom.coordsystem)
    elif isinstance(geom, Polygon):
        points = to_reverse_list(geom.points)
        result = Polygon(points, cs=geom.coordsystem)
        if len(geom.holes) == 0:
            return result
        for hole in geom.holes:
            hole = to_reverse_list(hole)
            result.holes.append(hole)
        return result


class RedirectLine:
    """Изменение направления линии для линий, полилиний и полигонов"""

    def __init__(self, title: str) -> None:
        self.title = title

    def on_triggered(self) -> None:
        spec = ProgressSpecification(description=self.title, flags=ProgressGuiFlags.CANCELABLE)
        task_manager.run_and_get(spec, self.__redirect)

    def __redirect(self, handler: AxipyProgressHandler):
        editable_table = helper.current_editable_table()
        if editable_table is None:
            return None

        selection = data_manager.selection

        def filter_types(f: Feature):
            g = f.geometry
            return isinstance(g, supported_types) and not isinstance(g, unsupported_types)

        features = list(filter(filter_types, selection.items()))  # type: List[Feature]

        handler.set_max_progress(len(features))
        updated_features = []
        for feature in features:
            handler.raise_if_canceled()
            feature.geometry = revert(feature.geometry)
            updated_features.append(feature)
            handler.add_progress(1)

        handler.prepare_to_write_changes()
        editable_table.update(updated_features)
        self.__notify_about_finish_process(len(updated_features))

    def __notify_about_finish_process(self, feature_count: int):
        if feature_count == 0:
            Notifications.push(
                self.title,
                axipy.tr(
                    f"Не удалось обновить ни одну геометрию. Список поддерживаемых типов: "
                    f'{", ".join(supported_types_string)}.'
                ),
                Notifications.Critical,
            )
        else:
            Notifications.push(
                self.title, axipy.tr(f"Было обновлено геометрий: {feature_count} "), Notifications.Success
            )
