import random

import numpy as np
from geopandas import GeoSeries
from shapely.geometry import Point, Polygon, MultiPolygon


def with_attributes(**attrs):
    def decorator(func):
        for key, value in attrs.items():
            setattr(func, key, value)
        return func
    return decorator


class Bench:

    def setup(self, *args):
        self.points = GeoSeries([Point(i, i) for i in range(100000)])

        triangles = GeoSeries([Polygon([(random.random(), random.random())
                                        for _ in range(3)])
                               for _ in range(1000)])
        triangles2 = triangles.copy().iloc[np.random.choice(1000, 1000)]
        triangles3 = GeoSeries([Polygon([(random.random(), random.random())
                                         for _ in range(3)])
                                for _ in range(10000)])
        triangles4 = GeoSeries([
            MultiPolygon([
                Polygon([(random.random(), random.random()) for _ in range(3)])
            ]) for _ in range(10000)])
        triangle = Polygon([(random.random(), random.random())
                            for _ in range(3)])
        self.triangles, self.triangles2 = triangles, triangles2
        self.triangles_big = triangles3
        self.multi_triangles = triangles4
        self.triangle = triangle

    @with_attributes(param_names=['op'],
                     params=[('contains', 'crosses', 'disjoint', 'intersects',
                              'overlaps', 'touches', 'within', 'geom_equals',
                              'geom_almost_equals', 'geom_equals_exact')])
    def time_binary_predicate(self, op):
        getattr(self.triangles, op)(self.triangle)

    @with_attributes(param_names=['op'],
                     params=[('contains', 'crosses', 'disjoint', 'intersects',
                              'overlaps', 'touches', 'within', 'geom_equals',
                              'geom_almost_equals')])  # 'geom_equals_exact')])
    def time_binary_predicate_vector(self, op):
        getattr(self.triangles, op)(self.triangles2)

    @with_attributes(param_names=['op'],
                     params=[('distance')])
    def time_binary_float(self, op):
        getattr(self.triangles, op)(self.triangle)

    @with_attributes(param_names=['op'],
                     params=[('distance')])
    def time_binary_float_vector(self, op):
        getattr(self.triangles, op)(self.triangles2)

    @with_attributes(param_names=['op'],
                     params=[('difference', 'symmetric_difference', 'union',
                              'intersection')])
    def time_binary_geo(self, op):
        getattr(self.triangles, op)(self.triangle)

    @with_attributes(param_names=['op'],
                     params=[('difference', 'symmetric_difference', 'union',
                              'intersection')])
    def time_binary_geo_vector(self, op):
        getattr(self.triangles, op)(self.triangles2)

    @with_attributes(param_names=['op'],
                     params=[('is_valid', 'is_empty', 'is_simple', 'is_ring')])
    def time_unary_predicate(self, op):
        getattr(self.triangles, op)

    @with_attributes(param_names=['op'],
                     params=[('area', 'length')])
    def time_unary_float(self, op):
        getattr(self.triangles_big, op)

    @with_attributes(param_names=['op'],
                     params=[('boundary', 'centroid', 'convex_hull',
                              'envelope', 'exterior', 'interiors')])
    def time_unary_geo(self, op):
        getattr(self.triangles, op)

    def time_unary_geo_representative_point(self, *args):
        getattr(self.triangles, 'representative_point')()

    def time_geom_type(self, *args):
        self.triangles_big.geom_type

    def time_bounds(self, *args):
        self.triangles.bounds

    def time_unary_union(self, *args):
        self.triangles.unary_union

    def time_buffer(self, *args):
        self.points.buffer(2)

    def time_explode(self, *args):
        self.multi_triangles.explode()


# TODO
# project, interpolate, affine_transform, translate, rotate, scale, skew
# cx indexer
