# optimal score ~ 0.67366

import matplotlib.pyplot as plt
import matplotlib.cm as cm

import numpy as np
import math
import random
import time


# from: http://extremelearning.com.au/how-to-generate-uniformly-random-points-on-n-spheres-and-n-balls/
def muller(N, d):
    points = []
    while len(points) < N:
        u = np.random.normal(0, 1, d)
        norm = np.sum(u ** 2) ** (0.5)
        r = random.random() ** (1.0 / d)
        pt = r * u / norm
        points.append(tuple(pt))
    return points

def tweak(point, delta):
    change = muller(1, 2)[0]
    new_x = point[0] + delta * change[0]
    new_y = point[1] + delta * change[1]
    return (new_x, new_y)


plt.ion()
plt.style.use('ggplot')
fig, (ax_contour, ax_score) = plt.subplots(1,2)

score_line, = ax_score.plot([], [])
xdata = []
ydata = []

explore_line, = ax_contour.plot([], [], linestyle='-', marker='.', color='red')
xline = []
yline = []
best = None


plt.subplots_adjust(bottom=0.15)
ax_contour.set_title("Contour Plot", fontsize=20)
ax_score.set_title("Value", fontsize=20)
plt.show(block=False)

props = dict(boxstyle="round", facecolor="blue", alpha=0.5)
text = ax_score.text(
    0,
    -0.1,
    f"Best Score: {best}",
    transform=ax_score.transAxes,
    fontsize=14,
    verticalalignment="top",
    bbox=props,
)

delta = 0.01
x = np.arange(-6.3, 6.3, delta)
y = np.arange(-6.3, 6.3, delta)
X, Y = np.meshgrid(x, y)
Z = X**2 + Y**2 + 25 * (np.sin(X)**2 + np.sin(Y)**2)
new_line = True
levels = np.arange(0, 100, 5)


CS = ax_contour.contour(X, Y, Z, levels=levels)

plt.pause(0.0001)

pt_x = random.random() * 12 - 6
pt_y = random.random() * 12 - 6
value = pt_x ** 2 + pt_y ** 2 + 25 * (math.sin(pt_x)**2 + math.sin(pt_y)**2)

while True:
    
    if new_line:
        explore_line, = ax_contour.plot([], [], linestyle='-', marker='.', color='red')
        xline = [pt_x]
        yline = [pt_y]
        explore_line.set_data(xline, yline)
        new_line = False
    else:
        xline.append(pt_x)
        yline.append(pt_y)
        explore_line.set_data(xline, yline)
    
    if best is None or value < best:
        best = value
        text.remove()
        text = ax_score.text(
            0,
            -0.05,
            f"Best Score: {best}",
            transform=ax_score.transAxes,
            fontsize=14,
            verticalalignment="top",
            bbox=props,
        )

    xdata.append(len(xdata))
    ydata.append(value)
    score_line.set_data(xdata, ydata)
    cur_x = ax_score.get_xlim()
    cur_y = ax_score.get_ylim()

    if xdata[-1] > cur_x[1]:
        ax_score.set_xlim((0, cur_x[1] + 100))
    if ydata[-1] > cur_y[1]:
        ax_score.set_ylim((0, max(ydata)))

    

    # time.sleep()
    plt.pause(0.00001)# ax_contour.clabel(CS, inline=True, fontsize=10)

    new_x = pt_x
    new_y = pt_y
    new_value = value

    tries = 0
    while new_value >= value:

        tries += 1
        if tries > 10000:
            plt.pause(0.5)
            tries = 0
            new_x = random.random() * 12 - 6
            new_y = random.random() * 12 - 6
            new_value = new_x ** 2 + new_y ** 2 + 25 * (math.sin(new_x)**2 + math.sin(new_y)**2)
            new_line = True
            break


        new_point = tweak((pt_x, pt_y), 0.1)
        new_x = new_point[0]
        new_y = new_point[1]
        new_value = new_x ** 2 + new_y ** 2 + 25 * (math.sin(new_x)**2 + math.sin(new_y)**2)

    
    # print(value, '->', new_value)
    # print((pt_x, pt_y), '->', (new_x, new_y))

    pt_x, pt_y = new_x, new_y
    value = new_value