from fractions import Fraction
from typing import (Callable,
                    Sequence,
                    Type)

from reprit.base import generate_repr

from ground.core.hints import (Contour,
                               Multipoint,
                               Multipolygon,
                               Multisegment,
                               Point,
                               Polygon,
                               Scalar,
                               Segment)
from .exact import (contour as exact_contour,
                    multipoint as exact_multipoint,
                    multipolygon as exact_multipolygon,
                    multisegment as exact_multisegment,
                    polygon as exact_polygon,
                    region as exact_region)
from .plain import (contour as plain_contour,
                    multipoint as plain_multipoint,
                    multipolygon as plain_multipolygon,
                    multisegment as plain_multisegment,
                    polygon as plain_polygon,
                    region as plain_region)
from .robust import (contour as robust_contour,
                     multipoint as robust_multipoint,
                     multipolygon as robust_multipolygon,
                     multisegment as robust_multisegment,
                     polygon as robust_polygon,
                     region as robust_region)

ContourCentroid = Callable[[Contour, Type[Point], Callable[[Scalar], Scalar]],
                           Point]
MultipointCentroid = Callable[[Multipoint, Type[Point]], Point]
MultipolygonCentroid = Callable[[Multipolygon, Type[Point]], Point]
MultisegmentCentroid = Callable[[Multisegment, Type[Point],
                                 Callable[[Scalar], Scalar]], Point]
PolygonCentroid = Callable[[Polygon, Type[Point]], Point]
RegionCentroid = Callable[[Contour, Type[Point]], Point]


class Context:
    __slots__ = ('_contour_centroid', '_multipoint_centroid',
                 '_multipolygon_centroid', '_multisegment_centroid',
                 '_polygon_centroid', '_region_centroid')

    def __init__(self,
                 contour_centroid: ContourCentroid,
                 multipoint_centroid: MultipointCentroid,
                 multipolygon_centroid: MultipolygonCentroid,
                 multisegment_centroid: MultisegmentCentroid,
                 polygon_centroid: PolygonCentroid,
                 region_centroid: RegionCentroid) -> None:
        self._contour_centroid = contour_centroid
        self._multipoint_centroid = multipoint_centroid
        self._multipolygon_centroid = multipolygon_centroid
        self._multisegment_centroid = multisegment_centroid
        self._polygon_centroid = polygon_centroid
        self._region_centroid = region_centroid

    __repr__ = generate_repr(__init__,
                             with_module_name=True)

    @property
    def contour_centroid(self) -> ContourCentroid:
        return self._contour_centroid

    @property
    def multipoint_centroid(self) -> MultipointCentroid:
        return self._multipoint_centroid

    @property
    def multipolygon_centroid(self) -> MultipolygonCentroid:
        return self._multipolygon_centroid

    @property
    def multisegment_centroid(self) -> MultisegmentCentroid:
        return self._multisegment_centroid

    @property
    def polygon_centroid(self) -> PolygonCentroid:
        return self._polygon_centroid

    @property
    def region_centroid(self) -> RegionCentroid:
        return self._region_centroid

    @staticmethod
    def segment_centroid(segment: Segment, point_cls: Type[Point],
                         *,
                         _half: Fraction = Fraction(1, 2)) -> Point:
        return point_cls(_half * (segment.start.x + segment.end.x),
                         _half * (segment.start.y + segment.end.y))


exact_context = Context(contour_centroid=exact_contour.centroid,
                        multipoint_centroid=exact_multipoint.centroid,
                        multipolygon_centroid=exact_multipolygon.centroid,
                        multisegment_centroid=exact_multisegment.centroid,
                        polygon_centroid=exact_polygon.centroid,
                        region_centroid=exact_region.centroid)
plain_context = Context(contour_centroid=plain_contour.centroid,
                        multipoint_centroid=plain_multipoint.centroid,
                        multipolygon_centroid=plain_multipolygon.centroid,
                        multisegment_centroid=plain_multisegment.centroid,
                        polygon_centroid=plain_polygon.centroid,
                        region_centroid=plain_region.centroid)
robust_context = Context(contour_centroid=robust_contour.centroid,
                         multipoint_centroid=robust_multipoint.centroid,
                         multipolygon_centroid=robust_multipolygon.centroid,
                         multisegment_centroid=robust_multisegment.centroid,
                         polygon_centroid=robust_polygon.centroid,
                         region_centroid=robust_region.centroid)
