import processing.core.*; 
import processing.data.*; 
import processing.event.*; 
import processing.opengl.*; 

import processing.video.*; 

import java.util.HashMap; 
import java.util.ArrayList; 
import java.io.File; 
import java.io.BufferedReader; 
import java.io.PrintWriter; 
import java.io.InputStream; 
import java.io.OutputStream; 
import java.io.IOException; 

public class painthuffer extends PApplet {

// based on ideas and code by Jordan Orelli and works of Robert Hodgin


Movie movie;

boolean imageswap = false;

public boolean sketchFullScreen() {
  return true;
}

class Particle {
  PVector origin;
  PVector forces;
  PVector velocity;
  boolean death;
  float mass;
  float age;
  PImage img;

  Particle(float x, float y, PImage img) {
    this.origin = new PVector(x, y);
    this.mass = 1.5f;
    this.age = 0.0f;
    this.velocity = new PVector(0, 0);
    this.img = img;
    this.death = false;
  }

  public void plan(PVector force) {
    this.velocity.mult(0.99f);
    force.div(this.mass);
    this.velocity.add(force);
    this.age+=1.0f;
  }

  public void move() {
    this.origin.add(this.velocity);
    if (this.outOfBounds()) {
      death = true;
      return;
    }
    int x = floor(map(this.origin.x, 0, width, 0, img.width));
    int y = floor(map(this.origin.y, 0, height, 0, img.height));
    if (img.pixels.length == 0) return;
    int c = img.pixels[y * img.width + x];
    float targetMass = CELL_SIZE * norm(brightness(c), 256, 0);
    if (targetMass > this.mass) {
      this.mass = min(this.mass * 1.1f, targetMass);
    } 
    else {
      this.mass = max(this.mass * 0.9f, targetMass);
    }
  }

  public boolean outOfBounds() {
    if (this.origin.x < 0 || this.origin.x >= width) {
      return true;
    }
    if (this.origin.y < 0 || this.origin.y >= height) {
      return true;
    }
    return false;
  }

  public void draw() {
    float n = norm(this.mass, 0, CELL_SIZE*2);
    float r = CELL_SIZE * n * n;
    
    
    int x = floor(map(this.origin.x, 0, width, 0, img.width));
    int y = floor(map(this.origin.y, 0, height, 0, img.height));
    int c = 0;
    if (y <img.height && x<img.width)
    c = img.pixels[y * img.width + x];
    if (brightness(c) > 192) if (imageswap) c+=(millis()*0.02f);
  float a = 16+abs(sin(millis()*0.04f)*192);
  float aa = random(32,192);
  a =92;
  fill(c,a);
  //if (a < 1.0 && !imageswap) {
  //  imageswap = true;
  //  particles.setImage(image2);
  //}
    float i = random(0.1f,1.1f);
    float ys = 16+r*4-abs(cos(millis()*0.1f*r)*8);
    if (brightness(c) < 32) fill(0,255);
    ellipse(this.origin.x, this.origin.y, (ys+4)/2, (ys+4)/8);
  }

  public void draw2() {
    float n = norm(this.mass, 0, CELL_SIZE);
    float r = CELL_SIZE * n * n;
    
    int x = floor(map(this.origin.x, 0, width, 0, img.width));
    int y = floor(map(this.origin.y, 0, height, 0, img.height));
    int c = 0;
    if (y <img.height && x<img.width)
     c = img.pixels[y * img.width + x];
    if (brightness(c) > 192)    if (imageswap) c+=(millis()*0.02f);
  float a = abs(sin(millis()*0.00004f)*92);
  float aa = random(32,192);
  fill(c,255);
  //if (a < 1.0 && !imageswap) {
  //  imageswap = true;
  //  particles.setImage(image2);
  //}
    float i = random(0.1f,1.1f);
//    float ys = 4+aa*0.1;
    if (brightness(c) < 32) fill(0,255);
    float ys = 16+r*4-abs(cos(millis()*0.1f*r)*8);
    ellipse(this.origin.x, this.origin.y, (ys+4)/4, (ys+4)/4);
  }

}

class Cell {
  ParticleSystem sys;
  ArrayList<Particle> myparticles;
  Cell[] neighbors;
  PVector origin;

  Cell(ParticleSystem sys, float x, float y) {
    this.sys = sys;
    this.myparticles = new ArrayList<Particle>();
    this.origin = new PVector(CELL_SIZE * x, CELL_SIZE * y);
  }
  
  public void plan() {
    for (Particle p : this.myparticles) {
      PVector force = this.repel(p);
      for (Cell cell : this.neighbors) {
        if (cell == null) {
          continue;
        }
        force.add(cell.repel(p));
      }
      force.limit(1.0f+cos(millis()*0.001f)*0.7f);
      p.plan(force);
    }
  }
  
  public void move() {
    for (int i = this.myparticles.size() - 1; i >= 0; i--) {
      Particle p = this.myparticles.get(i);
      p.move();
      
      if (p.death) {
        this.remove(p);
        particles.particleDump.remove(p);
        return;

      }
      
        if (!this.contains(p.origin.x, p.origin.y)) {
    this.remove(p);
    this.sys.place(p);
        }
      
    }
  }

  public void draw() {
    for (Particle p : this.myparticles) {
      p.draw();
    }
    debugDraw();
  }

  public void draw2() {
    for (Particle p : this.myparticles) {
      p.draw2();
    }
    debugDraw();
  }
  
  public void debugDraw() {
    if (!DEBUG) {
      return;
    }
    noFill();
    stroke(255, 0, 0);
    strokeWeight(1);
    rect(this.origin.x, this.origin.y, CELL_SIZE, CELL_SIZE);
  }

  public void add(Particle p) {
    this.myparticles.add(p);
  }
  
  public void remove(Particle p) {
    this.myparticles.remove(p);
  }
  public void removeAll() {
    this.myparticles.clear();
  }
  
  public PVector repel(Particle p) {
    PVector sum = new PVector(0, 0);
    float dist;
    PVector unit;
    for (Particle other : this.myparticles) {
      if (other == p) {
        continue;
      }
      dist = PVector.dist(p.origin, other.origin);
      if (dist > CELL_SIZE) {
        continue;
      }
      unit = PVector.sub(p.origin, other.origin);
      unit.normalize();
      dist = norm(dist, 0, CELL_SIZE);
      unit.mult((p.mass * other.mass) / (dist * dist));
      sum.add(unit);
    }
    return sum;
  }

  public boolean contains(float x, float y) {
    float dx = x - this.origin.x;
    if (dx < 0 || dx > CELL_SIZE) {
      return false;
    }
    float dy = y - this.origin.y;
    if (dy < 0 || dy > CELL_SIZE) {
      return false;
    }
    return true;
  }
}


public class ParticleSystem {
  Cell[][] cells;
  ArrayList<Particle> particleDump;
  PImage img;
  PImage img2;
  int cellWidth;
  int cellHeight;
  
  ParticleSystem(PApplet app, PImage img) {
    this.cellWidth = width / (int)CELL_SIZE;
    this.cellHeight = height / (int)CELL_SIZE;
    this.img = img;    
    this.particleDump = new ArrayList<Particle>();
    this.cells = new Cell[this.cellWidth][this.cellHeight];

    for (int x = 0; x < this.cellWidth; x++) {
      for (int y = 0; y < this.cellHeight; y++) {
        this.cells[x][y] = new Cell(this, x, y);
      }
    }

    for (int x = 0; x < this.cellWidth; x++) {
      for (int y = 0; y < this.cellHeight; y++) {
        this.cells[x][y].neighbors = this.neighbors(x, y);
      }
    }    
  }

  public void setImage(PImage img) {
    this.img = img;
  }

  public void removeAll() {
    for (int x = 0; x < this.cellWidth; x++) {
      for (int y = 0; y < this.cellHeight; y++) {
        this.cells[x][y].removeAll();
      }
    }
  }
  
  public void plan() {
    for (int x = 0; x < this.cellWidth; x++) {
      for (int y = 0; y < this.cellHeight; y++) {
        this.cells[x][y].plan();
      }
    }
  }
  
  public void move() {
    for (Cell[] row : this.cells) {
      for (Cell cell : row) {
        cell.move();
      }
    }
  }
  
  public void draw() {
    for (Cell[] row : this.cells) {
      for (Cell cell : row) {
        cell.draw();
      }
    }
  }

  public void draw2() {
    for (Cell[] row : this.cells) {
      for (Cell cell : row) {
        cell.draw2();
      }
    }
  }
    
  public void add(float x, float y) {
    if (this.particleDump.size() >= maxParticles) return;
    Particle p = new Particle(x, y, img);
    this.place(p);
    this.particleDump.add(p);
  }
  
  public void place(Particle p) {
    int cell_x = floor(p.origin.x / CELL_SIZE);
    int cell_y = floor(p.origin.y / CELL_SIZE);
    if (cell_x < 0 || cell_x >= this.cellWidth) {
      return;
    }
    if (cell_y < 0 || cell_y >= this.cellHeight) {
      return;
    }
    Cell cell = this.cells[cell_x][cell_y];
    cell.add(p);
  }
  
  public Cell[] neighbors(int x, int y) {
    Cell[] cells = {
      this.getCell(x-1, y-1),
      this.getCell(x  , y-1),
      this.getCell(x+1, y-1),
      this.getCell(x-1, y  ),
      this.getCell(x+1, y  ),
      this.getCell(x-1, y+1),
      this.getCell(x  , y+1),
      this.getCell(x+1, y+1)
    };
    return cells;
  }
  
  public Cell getCell(int x, int y) {
    if (x < 0 || x >= this.cellWidth || y < 0 || y >= this.cellHeight) {
      return null;
    }
    return this.cells[x][y];
  }

  public int getColor(float x, float y) {
    int _x = floor(map(x, 0, width, 0, this.img.width));
    int _y = floor(map(y, 0, height, 0, this.img.height));
    return this.img.pixels[_y * this.img.width + _x];
  }
}


float CELL_SIZE = 20.0f;
boolean DEBUG = false;
PImage image;
PImage image2;
ParticleSystem particles;

float nextClear = 10000;
int maxParticles = 4000;

public void setup() {
  orientation(LANDSCAPE);
  size(displayWidth, displayHeight);  
  
  movie = new Movie(this, "video.ogv");
  movie.loop();
  movie.speed(1.0f);
  
  particles = new ParticleSystem(this, image);
  background(0);
  noStroke();
  frameRate(60);
}

public void movieEvent(Movie m) {
  m.read();
}

PGraphics can;

int framec= 0;
public void draw() {
  fill(0,2);
  rect(0,0,width,height);
 // background(0,0,0);
  translate(width/2,height/2);
  rotate(cos(millis())*0.01f);
  translate(-width/2,-height/2);
  particles.setImage(movie);

    particles.add(this.width * 0.5f + random(-2.0f, 2.0f), 
      this.height * 0.5f + random(-2.0f, 2.0f));
    particles.add(this.width * 0.5f + random(-2.0f, 2.0f), 
      this.height * 0.5f + random(-2.0f, 2.0f));
    particles.add(this.width * 0.5f + random(-2.0f, 2.0f), 
      this.height * 0.5f + random(-2.0f, 2.0f));
    particles.add(this.width * 0.5f + random(-2.0f, 2.0f), 
      this.height * 0.5f + random(-2.0f, 2.0f));
    particles.add(this.width * 0.5f + random(-2.0f, 2.0f), 
      this.height * 0.5f + random(-2.0f, 2.0f));
    particles.add(this.width * 0.5f + random(-2.0f, 2.0f), 
      this.height * 0.5f + random(-2.0f, 2.0f));
    particles.add(this.width * 0.5f + random(-2.0f, 2.0f), 
      this.height * 0.5f + random(-2.0f, 2.0f));
    particles.add(this.width * 0.5f + random(-2.0f, 2.0f), 
      this.height * 0.5f + random(-2.0f, 2.0f));
    particles.add(this.width * 0.5f + random(-2.0f, 2.0f), 
      this.height * 0.5f + random(-2.0f, 2.0f));
    particles.add(this.width * 0.5f + random(-2.0f, 2.0f), 
      this.height * 0.5f + random(-2.0f, 2.0f));
    particles.add(this.width * 0.5f + random(-2.0f, 2.0f), 
      this.height * 0.5f + random(-2.0f, 2.0f));
    particles.add(this.width * 0.5f + random(-2.0f, 2.0f), 
      this.height * 0.5f + random(-2.0f, 2.0f));

      particles.plan();  
      particles.move();

    particles.draw2();

    particles.draw();

    framec++;

    if (framec > 25) {
      fill(255,16);
//      rect(0,0,width,height);
      framec = 0;
    }

    if (millis() > nextClear) {
//      tint(0,32);
//      rect(0,0,width,height);
   //   particles.removeAll();
      nextClear+=6000;
    }  


  }
  static public void main(String[] passedArgs) {
    String[] appletArgs = new String[] { "--full-screen", "--bgcolor=#666666", "--stop-color=#cccccc", "painthuffer" };
    if (passedArgs != null) {
      PApplet.main(concat(appletArgs, passedArgs));
    } else {
      PApplet.main(appletArgs);
    }
  }
}
