import numpy.random as numpy_random
import shapely.geometry as geom_shapely
import pycristoforo.geo.countries as eucountries_py
import pycristoforo.utils.constants as constants_py
from shapely.ops import triangulate as triangulate
from shapely.affinity import affine_transform as affine_transform
import random


def generate_random_triang_method(shape, num_points: int, country: str):
    """
    It generates 'points' (ex. 1, 5, 10, 1000) random latitude-longitude pairs using triangulation and affine transformation
    :param shape: shape in which you want to fit your coordinates . Polygon and MultiPolygon are the only shapes accepted
    :param num_points: how many random latitude-longitude pairs do you want to generate
    :param country: country name
    :return: list of features (geojson) containing the random points generated by the method
    """

    areas = []
    transforms = []
    points_in = []
    for t in triangulate(shape):
        areas.append(t.area)
        (x0, y0), (x1, y1), (x2, y2), _ = t.exterior.coords
        transforms.append([x1 - x0, x2 - x0, y2 - y0, y1 - y0, x0, y0])
    points = []
    list_of_points = []
    counter = 0
    for transform in random.choices(transforms, weights=areas, k=num_points):
        x, y = [random.random() for _ in range(2)]
        if x + y > 1:
            p = geom_shapely.Point(1 - x, 1 - y)
        else:
            p = geom_shapely.Point(x, y)
        points.append(affine_transform(p, transform))

    # checking if all the points generated are contained in shape
    for p in points:
        if shape.contains(p):
            points_in.append(p)
        else:
            new_p = regenerate_random_point(shape)
            while not(new_p.within(shape)):
                new_p = regenerate_random_point(shape)
            points_in.append(new_p)

    for p in points_in:
        ran_point = {
            "type": "Feature",
            "geometry":
                {
                    "type": "Point",
                    "coordinates": [p.xy[0][0], p.xy[1][0]]
                },
            "properties":
                {
                    "point": counter + 1,
                    "country": str(country)
                }
        }
        list_of_points.append(ran_point)
        counter += 1

    return list_of_points


def regenerate_random_point(shape):
    """
    It generates 'points' (ex. 1, 5, 10, 1000) random latitude-longitude pairs
    :param shape: shape in which you want to fit your geolocations . Polygon and MultiPolygon are the only shapes accepted
    :param points: how many random latitude-longitude pairs do you want to generate
    :param country: country name
    :return: list of features (geojson) containing the random points generated by the method
    """

    # getting min, max lat/lng
    min_lng = get_min_lng(shape)
    min_lat = get_min_lat(shape)
    max_lng = get_max_lng(shape)
    max_lat = get_max_lat(shape)

    # generate random float between [min_lng, max_lng)
    val1 = numpy_random.uniform(min_lng, max_lng)
    # generate random float between [min_lat, max_lat)
    val2 = numpy_random.uniform(min_lat, max_lat)

    # Point var created
    random_point = geom_shapely.Point(val1, val2)

    # checking if the generated point is withing the shape passed as input
    return random_point


def setup_shape(key: str):
    """
    This method returns the shape (or shapes) as 'Polygon' or 'MultyPoligon' type
    :param key: country ISO2 code/ISO3 code/name/FIPS code
    :return: the shape as Polygon or Multipolygon
    """

    # importing geojson file
    country_ids = eucountries_py.CountryList(constants_py.Constants.EU_PATH)
    uid = country_ids.get_by_key(key)
    shape_dict = country_ids.get_by_key(uid)

    # list of poligons (only used for MultiPolygon)
    poligons = []
    if shape_dict['type'] == "MultiPolygon":
        for polygon in shape_dict['coordinates']:
            for sub_polygon in polygon:
                pol = geom_shapely.Polygon(sub_polygon)
                poligons.append(pol)
        shape = geom_shapely.MultiPolygon(poligons)
    else:
        if shape_dict['type'] == "Polygon":
            shape = geom_shapely.Polygon(shape_dict['coordinates'][0])
        else:
            raise Exception('Error occurred during setting up the shape')

    return shape


def get_min_lat(shape):
    """
    It returns the minimum latitude given a shape
    :param shape: country shape
    :return: the min latitude
    """
    return shape.bounds[1]


def get_min_lng(shape):
    """
    It returns the minimum longitude given a shape
    :param shape: country shape
    :return: the min longitude
    """
    return shape.bounds[0]


def get_max_lat(shape):
    """
    It returns the maximum latitude given a shape
    :param shape: country shape
    :return: the max latitude
    """
    return shape.bounds[3]


def get_max_lng(shape):
    """
    It returns the maximum longitude given a shape
    :param shape: country shape
    :return: the max longitude
    """
    return shape.bounds[2]
