import processing.core.*; 
import processing.xml.*; 

import java.applet.*; 
import java.awt.Dimension; 
import java.awt.Frame; 
import java.awt.event.MouseEvent; 
import java.awt.event.KeyEvent; 
import java.awt.event.FocusEvent; 
import java.awt.Image; 
import java.io.*; 
import java.net.*; 
import java.text.*; 
import java.util.*; 
import java.util.zip.*; 
import java.util.regex.*; 

public class sketch_aug30b extends PApplet {


final int NUM_BALLS = 600;
final int GOAL_FRAME_RATE = 60;
ArrayList<Ball> myBalls = new ArrayList<Ball>();
final int MIN_BUFFER_FRAMES = 60;
Queue<PGraphics> FrameBuffer = new LinkedList<PGraphics>();
final float PROXIMITY_TO_SPHERE = 0.66f;
final int SIZE_TO_SPHERE = 10;

final int RENDER_WIDTH = 640;
final int RENDER_HEIGHT = 480;

int interlaceLines = 0;
boolean monochromeMode = false;

float startTime;
float lastDrawTime;
PGraphics grid;

public PGraphics prerenderedBall;

public void setup()
{
  //size(RENDER_WIDTH,RENDER_HEIGHT,P3D);
  size(640,480,P3D);
  noStroke();
  
  for ( int i=0; i < NUM_BALLS; i++ )
    myBalls.add(new Ball());
  
  GridPainter gridPainter = new GridPainter(); 
  gridPainter.flip(); 
  grid = gridPainter.getFrame();
  
  prerenderedBall = RenderBall();
  
  startTime = millis();
}

public PGraphics RenderBall()
{
  PGraphics ball = createGraphics(150,150,P3D);
  ball.beginDraw();
  ball.background(0);
  ball.translate(75,75);
  ball.lights();
  ball.noStroke();
  ball.fill(0,52,192);
  ball.sphere(50);
  ball.translate(-75,-75);
  ball.endDraw();
  return ball;
}

public void draw()
{
  float now = millis();
  
  if ( (myBalls.size() > 0) && (now - lastDrawTime) > GOAL_FRAME_RATE )
    myBalls.remove(myBalls.size()-1);
  /*
  if ( now - startTime > 30000 )
  {
    monochromeMode=true;
    interlaceLines=2;
  } 
*/
  
  lastDrawTime = now;
  
  background(0);  
  
  PGraphics frame = RenderFrame();
  /*
  if ( interlaceLines > 0 )
    Interlace(frame);
*/
  image(frame, 0, 0, frame.width, frame.height); 
  
  
  /*
  if ( now - startTime < 10000 )
    blend(grid, 0, 0, grid.width, grid.height, 0, 0, grid.width, grid.height, MULTIPLY);
  else if ( !monochromeMode )
    blend(grid, 0, 0, grid.width, grid.height, 0, 0, grid.width, grid.height, OVERLAY);
*/

}

public void Interlace(PGraphics frame)
{
  frame.loadPixels();

  for ( int i=0; i < frame.height*frame.width; i+= (2*interlaceLines)*frame.width )
  {
    for ( int j=i; j < i+(interlaceLines*frame.width); j++ )
    {
      if ( j < frame.width * frame.height )
        frame.pixels[j] = color(0,0,0);
      else
        break;
    }
  }
  
  frame.updatePixels();
}

public PGraphics RenderFrame()
{
  PGraphics pg = createGraphics(RENDER_WIDTH,RENDER_HEIGHT,P3D);
  pg.beginDraw();
  
  pg.noStroke();
  
  pg.lights();


  pg.translate(width/2,height/2);
  pg.scale(0.5f);
  pg.translate(-width/2,-height/2);
  
  
  for ( int i=0; i < myBalls.size(); i++ )
  {
    Ball ball = myBalls.get(i);
    ball.Draw(pg);
    ball.Move();
  }
  
  pg.endDraw();
  return pg;
}

public class Coord
{
  public float x,y,z;
  public Coord()
  {
    x=y=z=0;
  }
  public Coord(float _x, float _y, float _z)
  {
    x = _x;
    y = _y;
    z = _z;
  }
  public Coord(Coord copyFrom)
  {
    x = copyFrom.x;
    y = copyFrom.y;
    z = copyFrom.z;
  }
}

public class Vertex extends Coord
{
  public Vertex()
  {
    super();
  }
  public Vertex(Vertex copyFrom)
  {
    super((Coord)copyFrom);
  }
  public Vertex(float x, float y, float z)
  {
    super(x,y,z);
  }
  public void TranslateOn(PGraphics pg)
  {
    pg.translate(x,y,z);
  }
  public void TranslateOff(PGraphics pg)
  {
    pg.translate(-x,-y,-z);
  }
  public void Move(Vector vector)
  {
    x += vector.x;
    y += vector.y;
    z += vector.z;
  }
}
public class Vector extends Coord
{
  public Vector()
  {
    super();
  }
  public Vector(float x, float y, float z)
  {
    super(x,y,z);
  }    
}

class Ball
{
  final float STARTING_Y = height+300;
  final int MIN_BALL_SIZE = 10;
  final int MAX_BALL_SIZE = 100;
  
  private int mySize;
  private Vector myVector;
  private Vertex myVertex;
  private int myColor;
  private boolean hasBeenMonochromized = false;
  
  public Ball()
  {
    initRandom();
  }
  public void Draw(PGraphics pg)
  {    
   
    if ( monochromeMode && !hasBeenMonochromized )
    {
      ColorToMonochrome();
    } 
    
    pg.fill(myColor);
    myVertex.TranslateOn(pg);
    
    /*
    if ( mySize >= SIZE_TO_SPHERE && myVertex.z >= width*PROXIMITY_TO_SPHERE )
      pg.sphere(mySize);
    else
      pg.ellipse(0,0,mySize,mySize);
      */
    //pg.blend(prerenderedBall,0,0,mySize,mySize,0,0,mySize,mySize,SOFT_LIGHT);
    DrawPrerendered(pg);
    
    myVertex.TranslateOff(pg);
  }
  public void Move()
  {
    myVertex.Move(myVector);
    if ( myVertex.y < -height )
      initRandom();
  }
  private void DrawPrerendered(PGraphics pg)
  {
    //pg.scale(mySize/MAX_BALL_SIZE);
    //pg.image(prerenderedBall,0,0,mySize,mySize);
    pg.blend(prerenderedBall,0,0,150,150,(int)myVertex.x,(int)myVertex.y,mySize,mySize,DIFFERENCE);
    //pg.scale(-mySize/MAX_BALL_SIZE);
  }
  private void initRandom()
  {   
    mySize = (int)random(MIN_BALL_SIZE, MAX_BALL_SIZE);
    
    myVector = new Vector(0, random(-15,-0.5f) ,0);
       
    myVertex = new Vertex(
      random(0, width),
      STARTING_Y,
      random(0, width) );
    
    if ( monochromeMode )
    {
      myColor = color(0,(int)random(32,192),0);
    }    
    else    
    {
      myColor = color((int)random(32,192),(int)random(32,192),(int)random(32,192));
    }        
  }
  
  public void ColorToMonochrome()
  {
    myColor = color(0,green(myColor),0);
    hasBeenMonochromized = true;
  }
}



class GridPainter
{
  ArrayList<Hexagon> hexagons = new ArrayList<Hexagon>();
  float myAngle = -90;
  private PGraphics frame;
  
  public GridPainter()
  {
    setup();
    render();
  }
  
  public void setup()
  {
    BuildHexagonGrid();   
  }
  public PGraphics getFrame()
  {
    return frame;
  }
  public void render()
  {
    frame = createGraphics(width, height, P3D);
    frame.beginDraw();
    
    frame.noFill();
    //frame.stroke(255);
    
    frame.background(0);
    SetScale(frame);
    frame.rotateX(radians(myAngle));
    for ( int i=0; i < hexagons.size(); i++ )
    {
      Hexagon thisHexagon = hexagons.get(i);
      thisHexagon.Draw(frame);
  
    }
    frame.rotateX(-radians(myAngle));
    
    frame.endDraw();
  }
  
  private class Colour
  {
    public int r,g,b;
    public Colour(float _r, float _g, float _b)
    {
      r = (int)_r;
      g = (int)_g;
      b = (int)_b;
    }
  }
  
  public void flip()
  {
    frame.loadPixels();
    
    Stack<Colour[]> flipStack = new Stack<Colour[]>();
    
    for ( int i=0; i < frame.width * frame.height; i+=frame.width )
    {
      Colour[] thisRow = new Colour[frame.width];
      int index=0;
      for ( int j=i; j < i+frame.width; j++ )
      {                
        thisRow[index++] = new Colour(red(frame.pixels[j]), green(frame.pixels[j]), blue(frame.pixels[j]));
      }
      flipStack.push(thisRow);
    }
    
    int index=0;
    while ( !flipStack.isEmpty() )
    {
      Colour[] thisRow = flipStack.pop();
      
      for ( int j=0; j < thisRow.length; j++ )
      {
        Colour thisColor = thisRow[j];
        frame.pixels[index++] = color(thisColor.r, thisColor.g, thisColor.b);
      }
    }  

    frame.updatePixels();
  }
    
  public void SetScale(PGraphics img)
  {
    img.translate(width/2, height/2);
    img.scale(5);
    img.translate(-width/2, -height/2);
  }
  
  public void BuildHexagonGrid()
  {
    Vertex p = new Vertex();
    boolean altRow = false;
    
    float hexagonWidth=0;
    float hexagonHeight=0;
    
    int bright = 255;
    
    while ( p.y < height * 3 )
    {  
      while ( p.x < width )
      {
        Hexagon newHexagon = new Hexagon(p);
        newHexagon.setBrightness(bright);
        hexagons.add(newHexagon);
        hexagonWidth = newHexagon.getWidth();
        hexagonHeight = newHexagon.getHeight();
        p.x += hexagonWidth;
      }
  
      altRow = !altRow;
      if ( altRow )
        p.x = hexagonWidth/2;
      else
        p.x = 0;
        
      p.y += hexagonHeight-15;
      bright-=7;
      if ( bright < 0 ) bright = 0;
    }
  
  }

  class Hexagon
  {  
    private Vertex[] myPoints = new Vertex[6];
  
    /*
                0                            
              5   1                       
              4   2                       
                3                            
    */
    private int myBrightness=255;
    
    public Hexagon(Vertex pos)
    {
      init(pos);
    }
    
    private void init(Vertex pos)
    {
      myPoints[0] = new Vertex(pos);
      myPoints[1] = new Vertex(pos.x + 15, pos.y + 15, pos.z);
      myPoints[2] = new Vertex(pos.x + 15, pos.y + 30, pos.z);
      myPoints[3] = new Vertex(pos.x, pos.y + 45, pos.z);    
      myPoints[4] = new Vertex(pos.x - 15, pos.y + 30, pos.z);
      myPoints[5] = new Vertex(pos.x - 15, pos.y + 15, pos.z);
    }
    
    public void setBrightness(int bright)
    {
      myBrightness = bright;
    }
    
    public float getWidth()
    {    
      return myPoints[1].x - myPoints[5].x;
    }
    
    public float getHeight()
    {
      return myPoints[3].y - myPoints[0].y;
    }
    
    public void Draw(PGraphics img)
    {
      img.stroke(myBrightness);
      img.beginShape();

      for ( int i=0; i < 6; i++ )
      {
        img.vertex(myPoints[i].x, myPoints[i].y, myPoints[i].z);
      }
      img.vertex(myPoints[0].x, myPoints[0].y, myPoints[0].z);
      img.endShape();
         
    }
    
    
  }
}

  static public void main(String args[]) {
    PApplet.main(new String[] { "--bgcolor=#F0F0F0", "sketch_aug30b" });
  }
}
