import traceback
from enum import Enum
from itertools import filterfalse
from typing import List, MutableSequence

import axipy
from axipy import Notifications, Plugin
from axipy.concurrent import AxipyProgressHandler, ProgressSpecification, task_manager
from axipy.da import (
    Feature,
    Geometry,
    GeometryCollection,
    Line,
    LineString,
    Point,
    Polygon,
    Style,
)
from axipy.gui import selection_manager
from axipy.mi import Text

from ..helper import ensure_editable_table, normalize_if_not_valid

MIN_NODES_COUNT = 3


class ConvexHullMode(Enum):
    OneForAll = 0
    OneForEach = 1


def is_suitable(geom: Geometry) -> bool:
    if isinstance(geom, Text) or geom is None:
        return False
    return True


def selection_to_geometries(geometries: MutableSequence[Geometry]) -> List[Geometry]:
    res = []  # type: List[Geometry]
    for geom in geometries:
        if not is_suitable(geom):
            continue
        if isinstance(geom, GeometryCollection):
            it = filterfalse(lambda g: not is_suitable(g), geom)
            geometries.extend([sub_geom for sub_geom in it])
        else:
            res.append(geom)
    return res


def get_nodes_count(geom) -> int:
    """
    Определяем примерное число узлов. Если число узлов больше
    MIN_NODES_COUNT, возвращаем MIN_NODES_COUNT
    """
    if isinstance(geom, Point):
        return 1
    elif isinstance(geom, Text):
        return 0
    elif isinstance(geom, Line):
        return 2
    elif isinstance(geom, Polygon):
        # В валидном полигоне минимум 3 точки, а насколько больше 3-х не важно
        return MIN_NODES_COUNT
    elif isinstance(geom, LineString):
        # Бывают полилинии из 2-х точек
        polyline = geom  # type: LineString
        return min(MIN_NODES_COUNT, len(polyline.points))
    return MIN_NODES_COUNT


def is_enough_nodes(geometries: List[Geometry]) -> bool:
    """
    Для того чтобы построить ConvexHull нужно как минимум 3 точки
    """
    if len(geometries) >= MIN_NODES_COUNT:
        return True
    nodes_count = 0
    for geom in geometries:
        nodes_count = nodes_count + get_nodes_count(geom)
    return nodes_count >= MIN_NODES_COUNT


class OutlineAction:

    def __init__(self, iface: Plugin, title: str):
        super().__init__()
        self.title = title
        self.iface = iface

    @staticmethod
    def convexhull_one_for_all_geometries(geometries: List[Geometry]) -> List[Feature]:
        """
        Строим один окаймляющий контур для всех выделенных объектов
        """
        if len(geometries) == 0:
            return []
        cs = geometries[0].coordsystem
        collection = GeometryCollection(cs)
        for geom in geometries:
            collection.append(geom)
        res_geom = collection.convex_hull()
        res_style = Style.for_geometry(res_geom)
        feature = Feature(geometry=res_geom, style=res_style)
        return [feature]

    @staticmethod
    def convexhull_one_per_each_geometry(geometries: List[Geometry], handler: AxipyProgressHandler) -> List[Feature]:
        """
        Строим контур для каждого выделенного объекта отдельно
        """
        features = []  # type: List[Feature]
        handler.set_max_progress(len(geometries))
        for geom in geometries:
            handler.raise_if_canceled()
            if get_nodes_count(geom) < MIN_NODES_COUNT:
                continue
            geom_convexhull = geom.convex_hull()
            style_convexhull = Style.for_geometry(geom_convexhull)
            feature = Feature(geometry=geom_convexhull, style=style_convexhull)
            features.append(feature)
            handler.add_progress(1)
        return features

    def convexhull(self, ph: AxipyProgressHandler, mode: ConvexHullMode):
        geometries = [f.geometry for f in selection_manager.get_as_cursor() if f.has_geometry()]
        # Нормализуем геометрию перед выполнением геометрических преобразований
        geometries = normalize_if_not_valid(geometries)
        geometries = selection_to_geometries(geometries)
        if not is_enough_nodes(geometries):
            self.__notify_too_few_nodes()
            return
        table = ensure_editable_table()
        if mode == ConvexHullMode.OneForEach:
            features = self.convexhull_one_per_each_geometry(geometries, ph)
        else:
            features = self.convexhull_one_for_all_geometries(geometries)
        ph.prepare_to_write_changes()
        try:
            table.insert(features)
        except (Exception,):
            traceback.print_exc()
            self.__notify_about_busy_table()
            return
        self.__notify_about_finish(len(features))

    def on_triggered(self):
        spec = ProgressSpecification(description=self.title)
        task_manager.run_and_get(spec, self.convexhull, ConvexHullMode.OneForAll)

    def __notify_about_busy_table(self):
        Notifications.push(
            self.title,
            axipy.tr(
                "Невозможно внести изменения в таблицу. Проверьте, не занята " "ли таблица другой сессией ГИС Аксиомы."
            ),
            Notifications.Critical,
        )

    def __notify_too_few_nodes(self):
        Notifications.push(self.title, axipy.tr("Недостаточно узлов для построения контура"), Notifications.Warning)

    def __notify_about_finish(self, amount_added_features: int):
        if amount_added_features == 0:
            Notifications.push(self.title, axipy.tr(f"Не удалось обновить ни одну запись"), Notifications.Warning)
            return
        Notifications.push(
            self.title, axipy.tr(f"Количество добавленных записей: {amount_added_features}"), Notifications.Success
        )
