/*This program generates a shadow puppet show of a field of grass
  
  Program Structure:
  
  draw->painter->move/blow/cycle
               |
               |-Bush->Blade
               |-Grain->Stalk->Seed
               |-Cluster->Lion->Bract
  
  Way too much code.  Didn't have time to optimize or comment correctly.
  Sorry in advance.
*/

//Set up our background image, main arrays, and main parameters

  
  PImage backdrop;
  Bush[] grasses;
  Grain[] grains;
  Cluster[] clusters;
  int vel, force;





void setup(){
  size(400, 400);
  backdrop = loadImage("backdrop.jpg");
  //background(backdrop);
  smooth();
  noStroke();
  fill(20,220);
  frameRate(2); //As per the project
  
  //Initialize our arrays with enough stuff to populate the screen
  
  //Grass blades
  grasses = new Bush[30];
  for( int i = 0; i < grasses.length; i++){
    grasses[i] = new Bush(int(random(-40,width+40)), int(random(2,12)));
  }
  
  //Tall grass
  grains = new Grain[3];
  for( int i = 0; i < grains.length; i++){
    grains[i] = new Grain(int(random(-100,width+100)), int(random(2,5)));
  }
  
  //Dandy Lions
  clusters = new Cluster[2];
  for( int i = 0; i < clusters.length; i++){
    clusters[i] = new Cluster(int(random(-100,width+100)), int(random(1,3)));
  }
}

void draw(){
  background(backdrop);
  
  //Translate the matrix so the origin is in the bottom left.
  translate(0,height);
  scale(1,-1);
  
  //Randomize the wind force and the scroll velocity, ideally,
  //these would be mouse driven.
  vel = int(random(-20, 0));
  if( force > 0){
    force = force + int(random(-10, 0));
  }else if( force < -100){
    force = force + int(random(0, 10));
  }else {
    force = force + int(random(-10, 10));
  }
  
  //Our main drawing alg.
  painter(vel, force);
}






void painter(int speed, int wind){
  move(speed); //move everything.
  blow(wind);  //pass the wind value to the elements in the arrays
  cycle();     //push any object that goes too far left to the right.
               //and reset it's values.
}

//move, blow, and cycle are just for loops
//In the order of grass, grain, lions

void move(int speed){
  for( int i = 0; i < grasses.length; i++) {
    grasses[i].display();
    grasses[i].move(speed);
  }
  for( int i = 0; i < grains.length; i++) {
    grains[i].display();
    grains[i].move(speed);
  }
  for( int i = 0; i < clusters.length; i++) {
    clusters[i].display();
    clusters[i].move(speed);
  }
}

void blow(int wind){
    for( int i = 0; i < grasses.length; i++) {
      grasses[i].blow(wind);
    }
    for( int i = 0; i < grains.length; i++) {
      grains[i].blow(wind);
    }
    for( int i = 0; i < clusters.length; i++) {
      clusters[i].blow(wind);
    }
}

void cycle(){
  for( int i = 0; i < grasses.length; i++) {
    if (grasses[i].x < -40){
      grasses[i].init(width+int(random(40,80)),int(random(2,12)));
    }
  }
  for( int i = 0; i < grains.length; i++) {
    if (grains[i].x < -100){
      grains[i].init(width+int(random(100,180)),int(random(2,5)));
    }
  }
  for( int i = 0; i < clusters.length; i++) {
    if (clusters[i].x < -100){
      clusters[i].init(width+int(random(100,180)),int(random(1,3)));
    }
  }
}



//Basic blade of grass
class Blade{
  int x; 
  int pointXOff, pointYOff;
  int wind;
  
  Blade(int xPos, int xOff, int yOff){
      x = xPos;
      
      //assign the point for the tip based on parameters
      
      pointXOff = xOff + x;
      pointYOff = yOff;
  }
  
  void display(){
    beginShape();
      vertex(x,0);
      bezierVertex(x-6,10, x, pointYOff/2, pointXOff+wind,pointYOff);
      bezierVertex(x, pointYOff/2, x+12,10, x+6,0);
    endShape();
  }
}

//Bundle of grass
class Bush{
  int x, bladeCount;
  Blade[] bladeArray;
  int wind;
  
  Bush(int xpos, int count) {
    init(xpos, count);
  }
  
  //Initialization rig.
  void init(int xpos, int count) {
    x = xpos;
    bladeCount = count;
    
    bladeArray = new Blade[bladeCount];
    for( int i = 0; i < bladeCount; i++){
      bladeArray[i] = new Blade(int(random(x-15, x+15)),int(random(-40,40)),int(random(60,180)));
    }
  }
  
  //React to wind
  void blow(int strength){
    wind = strength;
    for ( int i = 0; i < bladeCount; i++){
      bladeArray[i].wind = strength;
    }
  }
  
  //Scroll when told to by move()
  void move(int offset) {
    x = x + offset;
    for ( int i = 0; i < bladeCount; i++){
      bladeArray[i].x = bladeArray[i].x + offset;
      bladeArray[i].pointXOff = bladeArray[i].pointXOff + offset;
    }
  }
  
  //Walk through the array and show blades.
  void display() {
    for( int i = 0; i < bladeCount; i++){
      bladeArray[i].display();
    }
    if (1 < wind || -1 > wind){
      wind = (wind*4)/5;
      blow(wind);
    }
    if (1 >= wind && -1 <= wind){
      wind = 0;
      blow(wind);
    }
  }
}



//Long leaf/seed at the end of a tall stalk of grass
class Seed{
  int x, y, u, v, flipper;
  
  Seed(int spawnX, int spawnY, int flip){
    x = spawnX;
    y = spawnY;
    flipper = flip;
    

    
    u = x + flipper*int(random(-80,-12));
    v =spawnY+int(random(-30,30));
  }
  
  void display(int offset){
    beginShape();
      vertex(x+offset+2*flipper,y);
      bezierVertex((x+u)/2+offset, (y+v)/2+3, (x+u)/2+offset, (y+v)/2+3, u+offset,v+offset/3);
      bezierVertex((x+u)/2+offset, (y+v)/2-3, (x+u)/2+offset, (y+v)/2-3, x+offset+2*flipper,y);
    endShape();
  }
}

//Stalk that is parent to the seeds, nothing special.
class Stalk{
  int x; 
  int pointXOff, pointYOff;
  int wind;
  int seedCount;
  Seed[] seedArray;
  
  Stalk(int xPos,int xOff,int yOff, int seeds){
    init(xPos, xOff, yOff, seeds);
  }
  
  void init(int xPos,int xOff,int yOff, int seeds){
    x = xPos;
    seedCount = seeds;
    pointXOff = xOff + x;
    pointYOff = yOff;
    
    int flipper = int(random(1,5));
    if (0 == flipper % 2) {
      flipper = -1;
    } else {
      flipper = 1;
    }
    
    seedArray = new Seed[seedCount];
    for( int i = 0; i < seedCount; i++){
      seedArray[i] = new Seed(pointXOff, pointYOff, flipper);
    }
  }
  
  void move(int offset) {
    x = x + offset;
    pointXOff = pointXOff + offset;
    for( int i = 0; i < seedCount; i++){
      seedArray[i].x = seedArray[i].x+ offset;
      seedArray[i].u = seedArray[i].u+ offset;
    }
  }
  
  void blow(int strength){
    wind = strength;
  }
  
  void display(){
    for( int i = 0; i < seedCount; i++){
      seedArray[i].display(wind);
    }
    
    stroke(20,220);
    strokeWeight(2);
    noFill();
    
    bezier(x,0, x-6,pointYOff/2, x, pointYOff/2, pointXOff+wind,pointYOff);
    
    fill(20,220);
    
    noStroke();
    if (-1 > wind){
      wind = (wind*4)/5;
      blow(wind);
    }
  }
}

//Bundle of stalks.
class Grain{
  int x, stalkCount;
  Stalk[] stalkArray;
  int wind;
  
  Grain(int xpos, int count) {
    init(xpos, count);
  }
  
  void init(int xpos, int count) {
    x = xpos;
    stalkCount = count;
    
    stalkArray = new Stalk[stalkCount];
    for( int i = 0; i < stalkCount; i++){
      stalkArray[i] = new Stalk(int(random(x-10, x+10)),int(random(-40,40)),int(random(80,220)),int(random(2,12)));
    }
  }
  
  void blow(int strength){
    wind = strength;
    for ( int i = 0; i < stalkCount; i++){
      stalkArray[i].wind = strength;
    }
  }
  
  void move(int offset) {
    x = x + offset;
    for ( int i = 0; i < stalkCount; i++){
      stalkArray[i].move(offset);
    }
  }
  
  void display() {
    for( int i = 0; i < stalkCount; i++){
      stalkArray[i].display();
    }
    if (1 < wind || -1 > wind){
      wind = (wind*4)/5;
      blow(wind);
    }
    if (1 >= wind && -1 <= wind){
      wind = 0;
      blow(wind);
    }
  }
}


//Dandy Lion tuft. Fairly simple alone.
class Bract{
  int x, y, u, v, flipper, dia;
  
  Bract(int spawnX, int spawnY){
    x = spawnX;
    y = spawnY;
    dia = int(random(15,25));

    u = x + int(random(-8,8));
    v = y + int(random(-3,8));
  }
  
  void display(int offset){
    stroke(20,100);
    strokeWeight(2);
    noFill();
    
    bezier(x+offset+2*flipper,y, (x+u)/2+offset, (y+v)/2+3, (x+u)/2+offset, (y+v)/2+3, u+offset,v);
    
    noStroke();
    fill(20,40);
    
    ellipse(u+offset, v, dia, dia);
    fill(20,220);
  }
  
  //Throw to the wind when told to
  void scatter(int wind){
    x = x + int(random(wind-25, 20));
    y = y + int(random(-20, -wind));
    
    u = x + int(random(-8,8));
    v = y + int(random(-3,8));
  }
}

//The dandy lion shaft.  The Lion will loose its
//bracts if the wind is stronger than the threshold.
class Lion{
  int x; 
  int pointXOff, pointYOff;
  int wind;
  int bractCount;
  int thresh = 45;
  boolean stuck;
  Bract[] bractArray;
  
  Lion(int xPos,int xOff,int yOff, int bracts){
    init(xPos, xOff, yOff, bracts);
  }
  
  void init(int xPos,int xOff,int yOff, int bracts){
    stuck = true;
    x = xPos;
    bractCount = bracts;
    pointXOff = xOff + x;
    pointYOff = yOff;
    
    bractArray = new Bract[bractCount];
    for( int i = 0; i < bractCount; i++){
      bractArray[i] = new Bract(pointXOff, pointYOff);
    }
  }
  
  void move(int offset) {
    x = x + offset;
    pointXOff = pointXOff + offset;
    for( int i = 0; i < bractCount; i++){
      bractArray[i].x = bractArray[i].x+ offset;
      bractArray[i].u = bractArray[i].u+ offset;
    }
  }
  
  void blow(int strength){
    wind = strength;
  }
  
  void display(){
    for( int i = 0; i < bractCount; i++){
      bractArray[i].display(wind);
    }

    stroke(20,220);
    strokeWeight(2);
    noFill();

    bezier(x,0, x-6,pointYOff/2, x, pointYOff/2, pointXOff+wind,pointYOff);
    
    noStroke();
    fill(20,220);
    
    ellipse(pointXOff+wind,pointYOff, 8,4);
    
    //If the tufts are attached, and the wind is strong,
    //toss them
    if( wind < -thresh && stuck){
      for( int i = 0; i < bractCount; i++){
        bractArray[i].scatter(wind);
        stuck = false;
      }
    //if they're already tossed, keep tossing.
    } else if (!stuck){
     for( int i = 0; i < bractCount; i++){
        bractArray[i].scatter(wind);
     }
    }
    
    if (-1 > wind){
      wind = (wind*4)/5;
      blow(wind);
    }
  }
}

//Bundle of dandy lions, virtually identical to Grain, probably should
//Have merged the two.
class Cluster{
  int x, lionCount;
  Lion[] lionArray;
  int wind;
  
  Cluster(int xpos, int count) {
    init(xpos, count);
  }
  
  void init(int xpos, int count) {
    x = xpos;
    lionCount = count;
    
    lionArray = new Lion[lionCount];
    for( int i = 0; i < lionCount; i++){
      lionArray[i] = new Lion(int(random(x-10, x+10)),int(random(-20,20)),int(random(60,160)),int(random(12,24)));
    }
  }
  
  void blow(int strength){
    wind = strength;
    for ( int i = 0; i < lionCount; i++){
      lionArray[i].wind = strength;
    }
  }
  
  void move(int offset) {
    x = x + offset;
    for ( int i = 0; i < lionCount; i++){
      lionArray[i].move(offset);
    }
  }
  
  void display() {
    for( int i = 0; i < lionCount; i++){
      lionArray[i].display();
    }
    if (1 < wind || -1 > wind){
      wind = (wind*4)/5;
      blow(wind);
    }
    if (1 >= wind && -1 <= wind){
      wind = 0;
      blow(wind);
    }
  }
}

Exercise 06: Write a function for filling the screen with a pattern that evokes