import processing.core.*; 
import processing.xml.*; 

import java.applet.*; 
import java.awt.*; 
import java.awt.image.*; 
import java.awt.event.*; 
import java.io.*; 
import java.net.*; 
import java.text.*; 
import java.util.*; 
import java.util.zip.*; 
import java.util.regex.*; 

public class Gliders extends PApplet {

// Gliders
// By Brian Prentice
// December 2008

int Width = 800;
int Height = 600;
int ControlHeight = 50;
int ScrollerWidth = 160;
int ScrollerHeight = 20;

int noGliders = 25;
int noCells = 15;
int dnaLength = 100;

int noRows;
int noColumns;
int gridSize = 10;
int xOffset;
int yOffset;
boolean showGrid = false;
Glider[] gliders;
boolean bidirectional = false;
boolean running = true;
boolean tracing = false;
boolean randomNoCells = false;
boolean randomDnaLength = false;
PFont font;
IntScroller noGlidersInput;
IntScroller noCellsInput;
IntScroller dnaLengthInput;

public void setup()
{
  size(800, 650, P2D);  //  Width, Height + ControlHeight
  font = loadFont("SansSerif-14.vlw");
  textFont(font, 14);
  textAlign(LEFT, CENTER);
  setupControls();
  setSize();
  makeGliders();
}

public void draw()
{
  if (( ! tracing) || (gliders == null))
    background(0);
  setSize();
  if (showGrid)
    drawGrid();
  checkScrollers();
  if (gliders != null)
  {
    for (int n = 0; n < noGliders; n++)
    {
      gliders[n].draw();
      if (running)
        gliders[n].update();
    }
  }
  noGlidersInput.draw();
  noCellsInput.draw();
  dnaLengthInput.draw();
}

public void keyPressed()
{
  noGlidersInput.keyPressed();
  noCellsInput.keyPressed();
  dnaLengthInput.keyPressed();
  if (key == ENTER)
    running = ! running;
  else if ((key == ' ') && (gliders != null))
    for (int n = 0; n < noGliders; n++)
      gliders[n].update();
  else if (key == 'c')
    gliders = null;
  else if ((key == 'g') && (! tracing))
    showGrid = ! showGrid;
  else if (key == 'r')
  {
    bidirectional = false;
    makeGliders();
  }
  else if (key == 'b')
  {
    bidirectional = true;
    makeGliders();
  }
  else if (key == 't')
    tracing = ! tracing;
  else if (key == 'n')
  {
    if (randomNoCells)
      noCellsInput.setName("No Cells: ");
    else
      noCellsInput.setName("Max No Cells: ");
    randomNoCells = ! randomNoCells;
  }
  else if (key == 'l')
  {
    if (randomDnaLength)
      dnaLengthInput.setName("DNA Length: ");
    else
      dnaLengthInput.setName("Max DNA Length: ");
    randomDnaLength = ! randomDnaLength;
  }
  else if (key == 'o')
    saveFrame();
  else if ((key == '+') && (gridSize < (width - 2)) && (gridSize < (height - 2)))
  {
    gridSize++;
    showGrid = true;
    gliders = null;
  }
  else if ((key == '-') && (gridSize > 1))
  {
    gridSize--;
    showGrid = true;
    gliders = null;
  }
}

public void setupControls()
{
  int spacing = (Width - 3 * ScrollerWidth) / 4;
  int x = (Width - 2 * spacing - 3 * ScrollerWidth) / 2;
  int y = Height + (ControlHeight - ScrollerHeight) / 2;
  noGlidersInput = new IntScroller("No Gliders: ", noGliders, 1, 100, x, y);
  x += ScrollerWidth + spacing;  
  noCellsInput = new IntScroller("No Cells: ", noCells, 1, 200, x, y);
  x += ScrollerWidth + spacing;  
  dnaLengthInput = new IntScroller("DNA Length: ", dnaLength, 1, 200, x, y);
}

public void setSize()
{
  noColumns = (width - 2) / gridSize;
  noRows = (height - ControlHeight - 2) / gridSize;
  xOffset = (width - noColumns * gridSize) / 2;
  yOffset = (height - ControlHeight - noRows * gridSize) / 2;
}

public void drawGrid()
{
  if (gridSize > 3)
  {
    stroke(255);
    for (int r = yOffset; r < (noRows + 1) * gridSize; r += gridSize)
      line(xOffset, r, xOffset + noColumns * gridSize, r);
    for(int c = xOffset; c < (noColumns + 1) * gridSize; c += gridSize)
      line(c, yOffset, c, yOffset + noRows * gridSize);
  }
  else
    noStroke();
}

public int checkInput(IntScroller scroller, int oldValue)
{
  int newValue = scroller.getValue();
  if (newValue != oldValue)
    gliders = null;
  return newValue;
}

public void checkScrollers()
{
  noGliders = checkInput(noGlidersInput, noGliders);
  noCells = checkInput(noCellsInput, noCells);
  dnaLength = checkInput(dnaLengthInput, dnaLength);
}

public void makeGliders()
{
  gliders = new Glider[noGliders];
  for (int g = 0; g < noGliders; g++)
  {
    int gliderColor = makeColor();
    int l = dnaLength;
    if (randomDnaLength)
      l = PApplet.parseInt(random(l)) + 1;      
    int n = noCells;
    if (randomNoCells)
      n = PApplet.parseInt(random(n)) + 1;      
    gliders[g] = new Glider(noColumns / 2, noRows / 2, gliderColor, l, n, bidirectional);
  }
}

public int makeColor()
{
  int[] rgb = new int[3];
  int i = PApplet.parseInt(random(3)); 
  rgb[i] = PApplet.parseInt(random(56)) + 200;
  rgb[(i + 1) % 3] = PApplet.parseInt(random(200));
  rgb[(i + 2) % 3] = PApplet.parseInt(random(100));
  return color(rgb[0], rgb[1], rgb[2]);
}

// Cell

class Cell
{
  private int x;
  private int y;
  private int dnaPointer;
  
  public Cell(int x, int y, int dnaPointer)
  {
    this.x = x;
    this.y = y;
    this.dnaPointer = dnaPointer;
  }
  
  public void update(int direction, int dnaLength)
  {
    switch (direction)
    {
      case 0:
        // N
        y--;
        break;
      case 1:
        // NE
        y--;
        x++;        
        break;
      case 2:
        // E
        x++;        
        break;
      case 3:
        // SE
        y++;
        x++;        
        break;
      case 4:
        // S
        y++;
        break;
      case 5:
        // SW
        y++;
        x--;        
        break;
      case 6:
        // W
        x--;        
        break;
      case 7:
        // NW
        y--;
        x--;        
        break;
    }
    if (x < 0)
      x = noColumns - 1;
    if (x == noColumns)
      x = 0;
    if (y < 0)
      y = noRows - 1;
    if (y == noRows)
      y = 0;
    dnaPointer = (dnaPointer + 1) % dnaLength;
  }
  
  public int getX()
  {
    return x;
  }
  
  public int getY()
  {
    return y;
  }  
  
  public void setDnaPointer(int y)
  {
    this.dnaPointer = dnaPointer;
  }
  
  public int getDnaPointer()
  {
    return dnaPointer;
  }  
}

// Glider

class Glider
{
  private int x;
  private int y;
  private int dnaLength;
  private int[] dna;
  private int noCells;
  private Cell[] cells;
  private int gliderColor;
  private boolean bidirectional;
  
  public Glider(int x, int y, int gliderColor, int dnaLength, int noCells, boolean bidirectional)
  {
    this.x = x;
    this.y = y;
    this.dnaLength = dnaLength;
    this.bidirectional = bidirectional;
    makeDna();
    this.noCells = noCells;
    makeCells();
    this.gliderColor = gliderColor;
  }
  
  private void makeDna()
  {
    dna = new int[dnaLength];
    if (bidirectional)
      makeDnaBidirectional();
    else
      makeDnaRandom();
  }
  
  private void makeDnaRandom()
  {
    for (int i = 0; i < dnaLength; i++)
      dna[i] = PApplet.parseInt(random(8));
  }
  
  private void makeDnaBidirectional()
  {
    int m = PApplet.parseInt(random(dnaLength));
    int d1 = PApplet.parseInt(random(8));
    int d2 = PApplet.parseInt(random(8));
    for (int i = 0; i < dnaLength; i++)
      if (i < m)
        dna[i] = d1;
      else
        dna[i] = d2;
  }
  
  private void makeCells()
  {
    cells = new Cell[noCells];
    for (int i = 0; i < noCells; i++)
    {
      cells[i] = new Cell(x, y, 0);
      for (int j = 0; j < i; j++)
        cells[j].update(dna[cells[j].getDnaPointer()], dnaLength);   
    }
  }
  
  public void draw()
  {
    fill(gliderColor);
    for (int i = 0; i < noCells; i++)
      rect(xOffset + cells[i].getX() * gridSize, yOffset + cells[i].getY() * gridSize, gridSize, gridSize);
  }
  
  public void update()
  {
    for (int i = 0; i < noCells; i++)
      cells[i].update(dna[cells[i].getDnaPointer()], dnaLength);
  }
}

// IntScroller

class IntScroller
{
  private String name;
  private int value;
  private int minValue;
  private int maxValue;
  private int x;
  private int y;
  private int w = ScrollerWidth;
  private int h = ScrollerHeight;
  private int textColor = color(0);
  private int backgroundColor = color(200);
  private int selectColor = color(128);
  
  public IntScroller(String name, int value, int minValue, int maxValue, int x, int y)
  {
    this.name = name;
    this.value = value;
    this.minValue = minValue;
    this.maxValue = maxValue;
    this.x = x;
    this.y = y;  
  }
  
  public void draw()
  {
    noStroke();
    if (isOver())
      fill(selectColor);
    else
      fill(backgroundColor);
    rect(x, y, w, h);
    fill(textColor);
    text(" " + name + str(value), x, y, w, h);  
  }
  
  public void keyPressed()
  {
    if ((key == CODED) && isOver())
    {
      switch (keyCode)
      {
        case RIGHT:
          value++;
          break;
        case UP:
          value += 10;
          break;
        case LEFT:
          value--;
          break;
        case DOWN:
          value -= 10;
          break;
      }
      if (value < minValue)
        value = minValue;
      else if (value > maxValue)
        value = maxValue;
    }    
  }
  
  private boolean isOver()
  {
    if ((mouseX >= x) && (mouseX < (x + w)) && (mouseY >= y) && (mouseY < (y + h))) 
      return true;
    else
      return false;
  }
  
  public int getValue()
  {
    return value;
  }
  
  public void setName(String name)
  {
    this.name = name;
  }
}


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

