#!/usr/bin/env python3
import numpy as np
import matplotlib.pyplot as plt
from alive_progress import alive_bar

img_res_x = 250
img_res_y = 250
total_pixels = img_res_x * img_res_y # so we don't gotta compute it every time

periods = 1
square_x = 0
square_y = 0

xmin = (-periods * np.pi) + (square_x * np.pi)
xmax = (periods  * np.pi) + (square_x * np.pi)
ymin = (-periods * np.pi) + (square_y * np.pi)
ymax = (periods * np.pi) + (square_y * np.pi)

#xmin = -10
#xmax = 10
#ymin = -10
#ymax = 10

escape = 10000
iterations = 255*3
c_x = 2 * np.pi
c_y = 2 * np.pi


grid = np.meshgrid(np.linspace(ymin, ymax, 200), np.linspace(xmin, xmax, 200))
#image = np.meshgrid(np.linspace(ymin, ymax, img_res_y), np.linspace(xmin, xmax, img_res_x))
image = np.zeros([img_res_y, img_res_x], dtype=np.double)


class point_charge():
  def __init__(self, x, y, c, mod):
    self.x = x
    self.y = y
    self.c = c
    self.mod = mod
  def get_field(self, to_x, to_y):
    if(self.mod):
      to_x = (to_x % self.mod)
      to_y = (to_y % self.mod)
    return np.array([
        ((self.c * (self.x - to_x)) / ((self.x - to_x)**2 + (self.y - to_y)**2)**1.5),
        ((self.c * (self.y - to_y)) / ((self.x - to_x)**2 + (self.y - to_y)**2)**1.5)])

#will remove all the point charge code if it turns out to be good enough to be impliemnted into openCL
#point_charges = [point_charge(-5, -5, 100), point_charge(-5, 5, -100), point_charge(5, 0, 100)]
#point_charges = [point_charge(-1,-1, 100, 10), point_charge(1,1,-100, 0)]
point_charges = []


#plt.ion()
ax = plt.gca()
fig = plt.gcf()
#ax.set_autoscale_on(False)
#ax.set_xlim([xmin, xmax])
#ax.set_ylim([ymin, ymax])

vector_arrows = None

def show_field():
  global vector_arrows
  grid_f = np.zeros_like(grid)
  for p in point_charges:
    grid_f += p.get_field(grid[0], grid[1])
  #plt.streamplot(grid[0], grid[1], grid_f[0], grid_f[1], density=5)
  vector_arrows = plt.quiver(grid[0], grid[1], grid_f[0], grid_f[1])
  plt.show(block=False)
  plt.pause(.1)


#show_field()

timestep = .1
def test_sim():
  particle_grid = np.meshgrid(np.linspace(ymin, ymax, 100), np.linspace(xmin, xmax, 100))
  pos = particle_grid
  acceleration = np.zeros_like(particle_grid)
  velocity = np.zeros_like(particle_grid)
  velocity = [np.ones_like(particle_grid[0]) * 1, np.ones_like(particle_grid[0]) * .5]
  mass = 10
  charge = 1
  particle_plot = ax.plot(velocity[0], velocity[1], 'bo', animated=True)
  #velocity += .1

  background = fig.canvas.copy_from_bbox(ax.bbox)
  ax.draw_artist(vector_arrows)
  fig.canvas.blit(fig.bbox)
  
  while True:
    fig.canvas.restore_region(background)
    field = np.zeros_like(particle_grid)
    # TODO can make this quicker by skipping initilization
    for p in point_charges:
      field += p.get_field(pos[0], pos[1])
    acceleration = ((charge * field) / mass) * timestep
    #print(acceleration)
    velocity += acceleration * timestep
    pos += velocity * timestep

    fig.canvas.restore_region(background)
    particle_plot[0].set_data(pos[0],pos[1])
    ax.draw_artist(particle_plot[0])
    fig.canvas.blit(fig.bbox)
    fig.canvas.flush_events()
    plt.pause(1/60)

    #fig.canvas.draw_idle()
#test_sim()

#exit(1)


max_timesteps = 10

def get_fractal_iter(img):
  next_x = img[1][y][x] / np.sin(img[0][y][x])
  img[0][y][x] = img[0][y][x] / np.sin(img[1][y][x])
  img[1][y][x] = next_x
  if (np.square(img[0][y][x]) + np.square(img[1][y][x])) >= escape:
    z[y][x] = i
    

#meshgrid makes things slower as we can't test individual points for breaking to infinity;
#however, I will fix that later.
cmap = plt.cm.viridis
cmap.set_bad((0,0,0))
cmap.set_over((0,0,0))
cmap.set_under((0,0,0))

with alive_bar(img_res_y, bar = 'filling', spinner = 'waves') as bar:
  for pix_y, y in enumerate(np.linspace(ymin, ymax, img_res_y)):
    for pix_x, x in enumerate(np.linspace(xmin, xmax, img_res_x)):
      on_x = x
      on_y = y
      for i in range(iterations):
        completed_ratio = (((pix_x * pix_y * 1)) / total_pixels)
        next_x = on_x/np.sin(on_y)
        on_y = on_y/np.sin(on_x)
        on_x = next_x


        # do physics here - we could just use vectors but i keep rewriting things
        timesteps = max_
        acceleration = np.array([0, 0], dtype=np.double)
        velocity = np.array([on_x, on_y], dtype=np.double) # maybe multiply by stuff
        pos = np.array([0,0], dtype=np.double)
        for t in range(timesteps):
          for p in point_charges:
            acceleration += p.get_field(on_x, on_y)
          velocity += acceleration
          pos += velocity * (timesteps)
          on_x += pos[0]
          on_y += pos[1]
        
        if on_x**2 + on_y**2 > escape:
          image[pix_x][pix_y] = i
          break
    bar()

ax.imshow(image, norm="log", aspect="auto", cmap=cmap)
plt.show()
        

# yeah, I shouldn't have switched to a meshgrid, oh well
#z = np.empty_like(image[0])
exit(0)
with alive_bar(img_res_x, bar = 'filling', spinner = 'waves') as bar:
  for y in range(img_res_y):
    for x in range(img_res_x):
      for i in range(iterations):
        if image[0][y][x] == np.NAN:
          continue
        next_x = image[1][y][x] / np.sin(image[0][y][x])
        image[0][y][x] = image[0][y][x] / np.sin(image[1][y][x])
        image[1][y][x] = next_x
        if (np.square(image[0][y][x]) + np.square(image[1][y][x])) >= escape:
          z[y][x] = i
          image[0][y][x] = np.NAN
          image[1][y][x] = np.NAN
          break
#        #do physics here
        
        

    bar()
#image = np.clip(image, -escape, escape)

