import math
import folium
import requests

from dataclasses import dataclass

OSRM_URL = 'http://127.0.0.1:5000'

@dataclass
class Point:
    latitude: float
    longitude: float
    approach: str

BAD_POINTS = [
    Point(57.13742907753131, 27.20497942152247, 'opposite'),
    Point(57.1572125851091, 27.405287230334434, 'opposite'),
    Point(57.1536428241892, 27.662818595800243, 'opposite'),
    Point(57.13742907753131, 27.20497942152247, 'curb'),
    Point(57.007511041473684, 27.137932637935975, 'curb'),
    Point(57.04240052916346, 27.61725387687487, 'opposite'),
    Point(57.155770998273056, 27.204962942017794, 'opposite'),
    Point(56.95718663018714, 27.34825734222834, 'opposite'),
    Point(57.00751274625871, 27.137927791691357, 'curb'),
    Point(56.99438843090881, 27.17055690820867, 'opposite'),
    Point(57.129120103299975, 27.26235018119029, 'opposite'),
    Point(57.12818857604232, 27.28028119931873, 'opposite'),
    Point(57.13198485164594, 27.279847867974595, 'curb'),
    Point(57.236561205869, 27.496260963919443, 'opposite'),
    Point(57.023623189874854, 27.65374348423633, 'curb'),
    Point(57.128578535034436, 27.26626684766672, 'opposite'),
    Point(57.155770998273056, 27.204962942017794, 'curb'),
    Point(57.0689958790128, 27.62816842411761, 'opposite'),
    Point(57.12818857604232, 27.28028119931873, 'curb'),
    Point(57.13198485164594, 27.279847867974595, 'opposite'),
    Point(57.18600936533689, 27.721468182714858, 'opposite'),
    Point(57.265810833705395, 27.663384864307876, 'curb'),
    Point(57.184817444783526, 27.6602484213285, 'curb'),
    Point(57.236561205869, 27.496260963919443, 'curb'),
    Point(57.1572125851091, 27.405287230334434, 'curb'),
    Point(57.08251956609028, 27.25390481093419, 'opposite'),
    Point(56.99438843090881, 27.17055690820867, 'curb'),
    Point(56.8904672439558, 27.10523303227006, 'curb'),
    Point(57.08251956609028, 27.25390481093419, 'curb'),
    Point(57.0480575084677, 27.229017370802488, 'curb'),
    Point(57.12627466940087, 27.395110301418406, 'curb'),
    Point(57.20665789896023, 27.423785714977, 'opposite'),
    Point(56.95718663018714, 27.34825734222834, 'curb'),
    Point(57.04240052916346, 27.61725387687487, 'curb'),
    Point(56.89046540667819, 27.105228906684406, 'curb'),
    Point(57.183196029629194, 27.692172611374442, 'curb'),
    Point(56.969166507179764, 27.079346385917287, 'curb'),
    Point(57.240393160774815, 27.50460642446817, 'opposite'),
    Point(57.183196029629194, 27.692172611374442, 'opposite'),
    Point(57.240393160774815, 27.50460642446817, 'curb'),
    Point(57.265810833705395, 27.663384864307876, 'opposite'),
    Point(57.143047769997004, 27.260740378958932, 'opposite'),
    Point(56.89994504190377, 27.344626797883723, 'opposite'),
    Point(57.1536428241892, 27.662818595800243, 'curb'),
    Point(57.184817444783526, 27.6602484213285, 'opposite'),
    Point(57.143047769997004, 27.260740378958932, 'curb'),
    Point(56.89046540667819, 27.105228906684406, 'opposite'),
    Point(56.89994504190377, 27.344626797883723, 'curb'),
    Point(56.94397958603552, 27.637140590207558, 'opposite'),
    Point(57.12627466940087, 27.395110301418406, 'opposite'),
    Point(57.00751274625871, 27.137927791691357, 'opposite'),
    Point(57.0689958790128, 27.62816842411761, 'curb'),
    Point(57.0480575084677, 27.229017370802488, 'opposite'),
    Point(57.20665789896023, 27.423785714977, 'curb'),
    Point(56.94397958603552, 27.637140590207558, 'curb'),
    Point(57.129120103299975, 27.26235018119029, 'curb'),
    Point(56.969166507179764, 27.079346385917287, 'opposite'),
    Point(57.128578535034436, 27.26626684766672, 'curb'),
    Point(57.1933147620072, 27.674077310337804, 'curb'),
    Point(57.023623189874854, 27.65374348423633, 'opposite'),
    Point(57.18600936533689, 27.721468182714858, 'curb'),
    Point(56.8904672439558, 27.10523303227006, 'opposite'),
    Point(57.1933147620072, 27.674077310337804, 'opposite'),
    Point(57.007511041473684, 27.137932637935975, 'opposite')
]

def draw_points(points):
    min_latitude = min(map(lambda point: point.latitude, points))
    max_latitude = max(map(lambda point: point.latitude, points))
    min_longitude = min(map(lambda point: point.longitude, points))
    max_longitude = max(map(lambda point: point.longitude, points))

    m = folium.Map(
            location=((min_latitude + max_latitude) / 2, (min_longitude + max_longitude) / 2),
            control_scale=True,
            zoom_start=10)

    for i, point in enumerate(points):
        folium.CircleMarker(
                location=(point.latitude, point.longitude),
                color='red', radius=5,
                fill=True, fill_opacity=0.1,
                tooltip=f'#{i}, {point.approach}').add_to(m)

    m.save("points.html")

def points_far_away(point_1, point_2):
    return math.fabs(point_1.latitude - point_2.latitude) > 0.1 or \
            math.fabs(point_1.longitude - point_2.longitude) > 0.1

def get_matrix_response(points, half_mode):
    coordinates = ';'.join([f'{point.longitude},{point.latitude}' for point in points])
    approaches = ';'.join([point.approach for point in points])

    if half_mode:
        total_count = len(points)
        half_count = total_count // 2

        sources = ';'.join(map(str, range(half_count)))
        destinations = ';'.join(map(str, range(half_count, total_count)))

    osrm_url = f'{OSRM_URL}/table/v1/driving/'
    osrm_url += f'{coordinates}?approaches={approaches}'

    if half_mode:
        osrm_url += f'&sources={sources}&destinations={destinations}'

    osrm_url += '&annotations=distance,duration&generate_hints=false'

    return requests.get(osrm_url).json()

def check_matrix_response(points, response):
    assert response['code'] == 'Ok'

    for i, row in enumerate(response['distances']):
        for j, distance in enumerate(row):
            if distance < 0 and points_far_away(points[i], points[j]):
                print(f'negative distance in cell ({i}, {j}) between {points[i]} and {points[j]}: {distance}')

def main():
    print('number of points:', len(BAD_POINTS))
    draw_points(BAD_POINTS)

    print('matrix request in full mode:')
    response = get_matrix_response(BAD_POINTS, False)
    check_matrix_response(BAD_POINTS, response)

    print('matrix request in half mode:')
    response = get_matrix_response(BAD_POINTS, True)
    check_matrix_response(BAD_POINTS, response)

if __name__ == '__main__':
    main()
