#!/usr/bin/python3
# -*- coding: utf-8 -*-
# -*- mode: Python -*-


'''

v0.1.0


LICENCE : CC

by cocoa

'''
from __future__ import print_function
import math
import random
import sys
import os
import time
import redis
import ast
import argparse


MAX_PARTICLES  = 50
MAX_TIME  = 500

argsparser = argparse.ArgumentParser(description="Dummy generator")
argsparser.add_argument("-f","--fps",help="Frame Per Second",default=30,type=int)
argsparser.add_argument("-v","--verbose",action="store_true",help="Verbose output")
# Redis Args
argsparser.add_argument("-i","--ip",help="IP address of the Redis server ",default="127.0.0.1",type=str)
argsparser.add_argument("-p","--port",help="Port of the Redis server ",default="6379",type=str)
#
argsparser.add_argument("-M","--max-particles",help="Max Particles. Default:{}".format(MAX_PARTICLES),default=MAX_PARTICLES,type=int)
argsparser.add_argument("-m","--max-time",help="Max Particles. Default:{}".format(MAX_TIME),default=MAX_TIME,type=int)


args        = argsparser.parse_args()
verbose     = args.verbose
ip          = args.ip
port        = args.port
max_particles = args.max_particles
max_time      = args.max_time

def debug(*args, **kwargs):
    if( verbose == False ):
        return
    print(*args, file=sys.stderr, **kwargs)


def rgb2int(rgb):
    #debug(name,"::rgb2int rbg:{}".format(rgb))
    return int('0x%02x%02x%02x' % tuple(rgb),0)

def spectrum_120( ):
    return ast.literal_eval(redisData["spectrum_10"])


def rgb2int(rgb):
    #debug(name,"::rgb2int rbg:{}".format(rgb))
    return int('0x%02x%02x%02x' % tuple(rgb),0)

def msNow():
    return time.time()

def refreshRedis():
    global redisData
    for key in redisKeys:
        redisData[key] = ast.literal_eval(r.get(key).decode('ascii'))

name="generator::redilisys_particles"

class UnpreparedParticle(Exception):
    pass

class Particle(object):
    def __init__(self, x, y, m):
        self.x = x
        self.y = y
        self.m = m
        self.dx = 0
        self.dy = 0
        self.connectedTo = []

        self.decay = random.randint(10,max_time)
        self.color = (random.randint(128,256) - int(12.8 * self.m),
                      random.randint(128,256) - int(12.8 * self.m),
                      random.randint(128,256) - int(12.8 * self.m))
        self.color = (255,255,255)
        #debug( self.color )

    def interact(self, bodies):
        self.connectedTo = []
        spec = redisData["spectrum_10"]
        power = int(sum(spec[4:6]))
        for other in bodies:
            if other is self:
                continue
            dx = other.x - self.x
            dy = other.y - self.y
            dist = math.sqrt(dx*dx + dy*dy)
            if dist == 0:
              dist = 1
            if dist < 100 and random.randint(0,power) > 0.5 :
              self.connectedTo.append(other)
              self.decay += 2
            factor = other.m / dist**2
            high_power = sum(spec[8:9]) if sum(spec[8:9]) != 0 else 0.01
            self.dx += (dx * factor * self.m)
            self.dy += (dy * factor  * self.m)
            #print "factor %f" % (factor,)

    def move(self):
        spec = redisData["spectrum_10"]
        x_friction = (2.2-(1+spec[7]/2))
        y_friction = (2.2-(1+spec[7]/2))
        #x_friction = 1.02
        #y_friction = 1.04
        self.dx /= x_friction if x_friction != 0 else 0.01
        self.dy /= y_friction if y_friction != 0 else 0.01
        self.x += self.dx
        self.y += self.dy
        if self.x > max_width:
          self.dx = - self.dx /8
          self.x = max_width
        if self.x < 1:
          self.dx = - self.dx /8
          self.x = 1
        if self.y > max_height:
          self.dy = - self.dy /4
          self.y = max_height
        if self.y < 1:
          self.dy = - self.dy /4
          self.y = 1
        #print "(%.2f,%.2f) -> (%.2f,%.2f)" % (ox, oy, self.x, self.y)

    def attractor(self,attractor):
      spec = redisData["spectrum_10"]
      power = sum(spec[0:4])/3
      # If we're going in the direction of center, reverse
      next_x = self.x + self.dx
      next_y = self.y + self.dy
      next_dx = attractor["x"] - self.x
      next_dy = attractor["y"] - self.y
      next_dist = math.sqrt(next_dx*next_dx + next_dy*next_dy)

      dx = attractor["x"] - self.x
      dy = attractor["y"] - self.y
      dist = math.sqrt(dx*dx + dy*dy)
      if dist == 0:
        dist = 1
      factor = power/ dist**2
      x_acceleration = (dx * factor * power * power)
      y_acceleration = (dx * factor * power * power)


      if next_dist > dist:
        self.dx -= x_acceleration * power
        self.dy -= y_acceleration * power
      else:
        self.dx += x_acceleration
        self.dy += y_acceleration


class Attractor(Particle):
    def move(self):
        pass


class ParticleViewer(object):
    def __init__(self, particles, size=(800,800)):
        (self.width, self.height) = size
        self.size = size
        self.particles = particles
        self.xoff = 0
        self.yoff = 0
        self.scalefactor = 1

    def redraw(self):

        pl = []
        drawnVectors = []
        for p in self.particles:
            x = int(self.scalefactor * p.x) - self.xoff
            y = int(self.scalefactor * p.y) - self.yoff
            if x > max_width:
              x = max_width
            if x < 1:
              x = 1
            if y > max_height:
              y = max_height
            if y < 1:
              y = 1

            color  = rgb2int(p.color)
            pl.append([x+1,y+1,0])
            pl.append([x+1,y+1,color])
            pl.append([x,y,color])

            for other in p.connectedTo:

              if [other,self] in drawnVectors:
                continue
              drawnVectors.append([other,self])
              pl.append([x,y,0])
              pl.append([x,y,color])
              pl.append([other.x,other.y,color])

        print(pl,flush = True)

    def decayParticles(self):
      for i,p in enumerate(self.particles):
        # Handle positional decay
        if p.decay == 0:
          del self.particles[i]
          continue
        p.decay = p.decay - 1
        # Handle color decay
        n = int(255 * (p.decay / max_time ))
        p.color = (n,n,n)


    def emitParticles(self):
        spec = redisData["spectrum_10"]
        power = sum(spec[6:])
        if len(self.particles ) > math.sqrt(max_particles):
          if len(self.particles) > max_particles:
            return
          if random.random() > power:
            return
        # x is either left or right
        d = 600
        rx = 100 if random.randint(0,1) else 700
        #rx = random.randint(1,max_width)
        ry = random.randint(1,max_height)
        spec = redisData["spectrum_10"]
        m = random.randint(1,1+int(10*spec[7]))
        particles.append(Particle(rx, ry, m))


    def tick(self):
        self.decayParticles()
        self.emitParticles()
        for p in self.particles:
            p.interact(self.particles)
            p.attractor({
              "x":max_width/2,
              "y":max_height/2
              })
        for p in particles:
            p.move()
        self.redraw()

    def scale(self, factor):
        self.scalefactor += factor




max_width = 800
max_height = 800
redisKeys   = ["spectrum_120","spectrum_10"]
redisData = {}
redisLastHit = msNow() - 99999
r = redis.Redis(
        host=ip,
        port=port)

white = 16777215

refreshRedis()
if __name__ == "__main__":
    particles = []
#    particles.append(Attractor(320, 200, 10))
#    particles.append(Attractor(100, 100, 10))

    win = ParticleViewer(particles)
    try:
        while True:
            win.tick()
            refreshRedis()
            time.sleep(.03)

    except KeyboardInterrupt:
        pass
