import json
import random
import functools
import datetime
import multiprocessing
from math import sqrt, acos, cos, sin, radians, degrees
from PIL import Image

img_north_border = radians(54.8364)
img_west_border = radians(14.1229)
img_south_border = radians(49.0024)
img_east_border = radians(24.1455)
img_path = "poland.bmp"

img = Image.open(img_path)
print(img.size)

p_horiz_scale = img.width  / (img_east_border  - img_west_border)
p_verti_scale = img.height / (img_north_border - img_south_border)

with open("pos.json") as df:
    data = json.loads(df.read())
    frog_shop_positions = [(radians(x['lat']), radians(x['lon'])) for x in data]

# @functools.cache
# def dist(lat, lon):
#     return min([sqrt((lat-i[0])**2 + (lon-i[1])**2) for i in frog_shop_positions])


def gcd(a: tuple[float, float], b: tuple[float, float]) -> float:
    return acos(sin(a[0])*sin(b[0]) + cos(a[0])*cos(b[0])*cos(abs(a[1]-b[1])))

def dist(lat, lon):
    return min([gcd((lat, lon), i) for i in frog_shop_positions])

def in_poland(lat, lon, im):
    if lat < img_south_border or lat >= img_north_border or lon < img_west_border or lon >= img_east_border:
        return False
    offlat = lat - img_south_border
    offlon = lon - img_west_border
    ppos = (offlon * p_horiz_scale, offlat * p_verti_scale)
    ppos = (ppos[0], img.height - ppos[1] - 1)
    # try:
    return im.getpixel(ppos) == (0, 0, 0)
    # except Exception as e:
    #     print("FUCK")
    #     print(e)
    #     print(lat, lon)
    #     exit(1)

def start_point(im):
    lat, lon = 0, 0
    while not in_poland(lat, lon, im):
        lat = random.uniform(img_south_border, img_north_border)
        lon = random.uniform(img_west_border, img_east_border)
    return (lat, lon)

epsilon = 0.00005

def get_next_point(curr, im):
    point_candidates = [
        (curr[0] + epsilon, curr[1]),
        (curr[0] - epsilon, curr[1]),
        (curr[0], curr[1] + epsilon),
        (curr[0], curr[1] - epsilon),
    ]
    points = [x for x in point_candidates if in_poland(x[0], x[1], im)]
    best_dist = dist(curr[0], curr[1])
    best = curr
    for point in points:
        d = dist(point[0], point[1])
        if d > best_dist:
            best = point
    return best

def find_local_best(start, im):
    iters = 0
    curr = None
    next = start
    start_time = datetime.datetime.now()
    while curr != next:
        curr = next
        print(curr)
        iters += 1
        next = get_next_point(curr, im)
    print(f"Local minimum at {next}, {dist(next[0], next[1])} ({datetime.datetime.now() - start_time})")
    return next

def search(id):
    best_point = None
    best_dist = 0
    img = Image.open(img_path)
    for i in range(2000):
        print(f"Runner {id} at attempt {i}")
        start = start_point(img)
        local = find_local_best(start, img)
        if dist(local[0], local[1]) > best_dist:
            best_point = local
            best_dist = dist(local[0], local[1])
            print(f"Better than previous, new best for {id} is {best_point} with {best_dist}")
    return (best_point, best_dist)

# with multiprocessing.Pool(16) as p:
#     found = p.map(search, range(40))
#     best_overall = max(found, key=lambda x: x[1])
#     print(f"Found best point at {degrees(best_overall[0][0]), degrees(best_overall[0][1])} with distance {degrees(best_overall[1])}")

#print(degrees(dist(radians(49.92), radians(14.19))))

fuck = find_local_best(
    (radians(52), radians(19)), img
)
print(degrees(fuck[0]), degrees(fuck[1]))