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

import ddf.minim.spi.*; 
import ddf.minim.signals.*; 
import ddf.minim.*; 
import ddf.minim.analysis.*; 
import ddf.minim.ugens.*; 
import ddf.minim.effects.*; 

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 demo extends PApplet {

/* 
 * Code for starting a demo project
 *
 * Nothing extra except standard libraries
 * bundled with Processing 
 */

// Minim is needed for the music playback.







// These control how big the opened window is.
// Before you release your demo, set these to 
// full HD resolution (1920x1080).
int CANVAS_WIDTH = 1920;
int CANVAS_HEIGHT = 1080;
float ASPECT_RATIO = (float)CANVAS_WIDTH/CANVAS_HEIGHT;
int currentEffect = 0;

Effect effects[];

// You can skip backwards/forwards in your demo by using the 
// arrow keys; this controls how many milliseconds you skip
// with one keypress.
int SONG_SKIP_MILLISECONDS = 2000;

// Needed for audio
Minim minim;
AudioPlayer song;


/*
 * Processing's setup method.
 *
 * Do all your one-time setup routines in here.
 */
Aquarium a;
public void setup() {
  // Set up the drawing area size and renderer (P2D / P3D).
  size(CANVAS_WIDTH, CANVAS_HEIGHT, P2D);
  frameRate(60);
  a = new Aquarium();
  a.setup();

  minim = new Minim(this);
  song = minim.loadFile("demobiisi2.wav");
  song.play();
  textSize(32);
  textSize(40);
}


/*
 * Processing's drawing method
 */
public void draw() {
  if (!song.isPlaying())
    exit();

  float secs = song.position() / 1000.0f;
  clear();
  a.draw(secs);

  //24.375
}

public void keyPressed() {
  if (key == CODED) {
    // Left/right arrow keys: seek song
    if (keyCode == LEFT) {
      song.skip(-SONG_SKIP_MILLISECONDS);
    } 
    else if (keyCode == RIGHT) {
      song.skip(SONG_SKIP_MILLISECONDS);
    }
  }
  // Enter: spit out the current position
  // (for syncing)
  else if (key == ENTER) {
    print(song.position() + ", ");
  }
}
class Aquarium implements Effect {
  Background background;
  EvolveBlob evolveBlob;
  Puu puu;
  Ruusu ruusu;
  Group group;
  Greets greets;
  Demoni demoni;
  float dir = -1;
  Kuplat kuplat;
  JatkuvatKuplat jkuplat;

  float treeMultiplier = 0;
  float treeColor = 0;
  float puuSvengi = 0;
  float treeAlpha = 0;

  int mode;

  public void setup() {
    mode = 0;

    greets = new Greets();
    ruusu = new Ruusu();
    group = new Group();
    demoni = new Demoni();
    ruusu.setup();
    background = new Background();
    evolveBlob = new EvolveBlob();
    evolveBlob.setup();
    //fishes = new Fishes();
    puu = new Puu();
    background.setup();
    //fishes.setup();
    puu.setup();
    kuplat = new Kuplat();
    jkuplat = new JatkuvatKuplat();
    jkuplat.setup();
  }

  public float lastSecs = 0;

  int prevBar = -1;
  int prevBeat = -1;

  public void draw(float secs) {
    int curBar = floor(secs / (6.0f / 8.0f));
    int curBeat = floor(secs / (6.0f / 8.0f) * 4);

    boolean didBarChange = curBar != prevBar;
    prevBar = curBar;

    boolean didBeatChange = curBeat != prevBeat;
    prevBeat = curBeat;

    float delta = secs - lastSecs;
    if (lastSecs == 0.0f) {
      delta = 0; }
    lastSecs = secs;

    if (secs > 5 && mode < 1) {
      mode = 1;
    }
    if (secs > 25 && mode < 2) {
      mode = 2;
      kuplat.setup();
    }
    if (secs > 28 && mode < 3) {
      mode = 3;
    }
    if (secs > 38 && mode < 4) {
      mode = 4;
      kuplat.setup();
    }
    if (secs > 41 && mode < 5) {
      mode = 5;
    }

    if (secs > 61 && mode < 6) {
      mode = 6;
    }

    if (secs > 81 && mode < 7) {
      mode = 7;
    }

    if (secs > 101 && mode < 8) {
      mode = 8;
    }
    if (secs > 121 && mode < 9) {
      mode = 9;
    }

    if (secs > 141 && mode < 10) {
      mode = 10;
      jkuplat.stop();
    }

    switch (mode) {
      case 0: //FADE IN
        background.multR += 0.15f * delta;
        if (background.multR > 1)
          background.multR = 1;

        background.multB += 0.15f * delta;
        if (background.multB > 1)
          background.multB = 1;
        break;

      case 1: // GROW TREES
        treeAlpha += 0.3f * delta;
        if (treeAlpha > 1)
          treeAlpha = 1;

        treeMultiplier += 0.1f * delta;
        if (treeMultiplier > 1)
          treeMultiplier = 1;
        treeColor += 0.1f * delta;
        if (treeColor > 1) 
          treeColor = 1;
        break;

      case 2: // BUBBLES
        treeMultiplier -= 0.4f * delta;
        if (treeMultiplier < 0)
          treeMultiplier  = 0;
        treeAlpha -= 0.5f * delta;
        if (treeAlpha < 0)
          treeAlpha = 0;
        ruusu.alpha += 0.34f*delta;
        if (ruusu.alpha > 1)
          ruusu.alpha = 1;
        break;

      case 3: // BRING IN THE DNA ROSE WHATEVER
        break;

      case 4: // MORE BUBBLES
        ruusu.alpha -= 0.5f*delta;
        if (ruusu.alpha < 0)
          ruusu.alpha = 0;
        break;

      case 5: // BRING IN THE BLOB, GROW THE TREES BACK
        treeAlpha += 0.25f * delta;
        if (treeAlpha > 1)
          treeAlpha = 1;

        if (treeMultiplier < 1)
          treeMultiplier += 0.10f * delta;
        else
          treeMultiplier = 1;
        puuSvengi += 0.1f * delta;
        if (puuSvengi > 1)
          puuSvengi = 1;


        break;

      case 6: // EVOLVE TO STARFISH
        evolveBlob.f.mode += 0.1f * delta;
        if (evolveBlob.f.mode > 0)
          evolveBlob.f.mode = 0;

        background.multG += 0.01f * delta;
        if (background.multG > 1)
          background.multG = 1;

        background.multB += 0.01f * delta;
        if (background.multB > 1)
          background.multB = 1;

        break;

      case 7: // EVOLVE TO FISH
        evolveBlob.f.mode += 0.1f * delta;
        if (evolveBlob.f.mode > 1)
          evolveBlob.f.mode = 1;

        background.multG += 0.02f * delta;
        if (background.multG > 1)
          background.multG = 1;

        background.multB += 0.02f * delta;
        if (background.multB > 1)
          background.multB = 1;

        break;

      case 8: // EVOLVE TO SQUID
        evolveBlob.f.mode += 0.1f * delta;
        if (evolveBlob.f.mode > 2)
          evolveBlob.f.mode = 2;

        background.multG += 0.02f * delta;
        if (background.multG > 1)
          background.multG = 1;

        background.multB += 0.02f * delta;
        if (background.multB > 1)
          background.multB = 1;
        break;

      case 9: // EVOLVE LOOP
        evolveBlob.f.mode += 0.4f * delta * dir;
        if (evolveBlob.f.mode > 2) {
          evolveBlob.f.mode = 2;
          dir = -1;
        }
        if (evolveBlob.f.mode < -1) {
          evolveBlob.f.mode = -1;
          dir = 1;
        }
        puuSvengi += 0.2f * delta;
        break;

      case 10: // FADEOUT
        background.multB -= 0.1f * delta;
        if (background.multB < 0)
          background.multB = 0;
        background.multR -= 0.1f * delta;
        if (background.multR < 0)
          background.multR = 0;
        background.multG -= 0.1f * delta;
        if (background.multG < 0)
          background.multG = 0;

        treeAlpha -= 0.1f * delta;
        if (treeAlpha < 0)
          treeAlpha = 0;

        treeMultiplier -= 0.1f * delta;
        if (treeMultiplier < 0)
          treeMultiplier = 0;
          evolveBlob.moveout = true;

        break;
    }

    if (didBarChange && mode > 4)
      evolveBlob.pulse();

    background.draw(secs);

    if (treeMultiplier > 0.01f)
    {
      float r = height/12;
      r += cos(TWO_PI * secs  / (8.0f / 6.0f)) * 2 * puuSvengi * 7;
      r *= treeMultiplier;
      
      float x = width/2-(width/4)+(width/8);
      float y = 0-(r/1.3f);     
      puu.draw(secs, x, y, r, 0.5f, 0.3f, -0.2f, treeColor, treeAlpha);

      
      r = height/13;
      r += cos(TWO_PI * secs / (8.0f / 6.0f)) * 2 * puuSvengi * 7;
      r *= treeMultiplier;
      x = width/2+(width/4);
      y = height/15;     
      puu.draw(secs, x, y, r, 0.5f, 0.2f, -0.4f, treeColor, treeAlpha);    

      r = height/15;
      r += cos(TWO_PI * secs / (8.0f / 6.0f)) * 2 * puuSvengi * 7;
      r *= treeMultiplier;
      x = width/(width/4);
      y = 0-1.5f*r;     
      puu.draw(secs, x, y, r, 0.5f, 0.4f, -0.2f, treeColor * 0.90f, treeAlpha); 

      r = height/17;
      r += cos(TWO_PI * secs / (8.0f / 6.0f)) * 2 * puuSvengi * 7;
      r *= treeMultiplier;
      x = width/2;
      y = 0-2*r;
      puu.draw(secs, x, y, r, 0.5f, 0.2f, -0.2f, treeColor * 0.85f, treeAlpha); 
    }

    //fishes.draw(secs);

    if (mode == 1) {
      group.draw(delta);
      demoni.draw(delta);
    }


    if (mode == 3 || mode == 4)
      ruusu.draw(secs);

    if (mode >= 5)
      evolveBlob.draw(secs);

    if (secs > 84) {
      jkuplat.draw(secs);
    }

    if (mode == 9)
      greets.draw(delta);
    
    if (mode == 2 || mode == 4)
      kuplat.draw(secs);
  }
}
class Background implements Effect {
  Color colors[];
  int numLines;
  int lineHeight;
  int numSplits; 

  float scaleX, scaleY;
  float multR, multG, multB;

  public void setup() {
    this.numLines = 34;
    this.lineHeight = ceil((float)height / (float)this.numLines);
    this.numSplits = 2;

    scaleX = 1;
    scaleY = 1;

    this.colors = new Color[6];
    this.colors[0] = new Color(0, 70, 134);
    this.colors[1] = new Color(20, 92, 128);
    this.colors[2] = new Color(0, 100, 140);
    this.colors[3] = new Color(20, 127, 158);
    this.colors[4] = new Color(0, 100, 130);
    this.colors[5] = new Color(20, 92, 118);
  }

  public int[] getSplitPoints(int line, float secs) {
    int numSins = 15;
    int[] result = new int[numSins];

    int x = 50;
    int deltaX = ceil(width / (numSins + 1) * scaleX);

    for (int i = 0; i < numSins; ++i) {
      float modifier = i % 2 - 1 * sin(secs + i % 4);
      result[i] = ceil(sin((float)line * 0.12f * (i % 3 + 1) + i + (1.0f + secs / 3.0f)) * width / 20.0f * scaleX * modifier + x);
      x += deltaX;
    };
    result = sort(result);
    return result;
  }

  public void draw(float secs) {
    resetMatrix();
    noStroke();
    int[] previousSplitPoints = this.getSplitPoints(0, secs);
    for (int i = 1; i < this.numLines + 1; ++i) {
      int y = (i - 1) * this.lineHeight;
      int[] splitPoints = this.getSplitPoints(i, secs);
      int x1 = 0, previousX1 = 0; 
      int colorIndex = 0;
      for (int j = 0; j <= splitPoints.length; ++j) {
        int x2, previousX2;
        if (j == splitPoints.length) {
          x2 = width;
          previousX2 = width;
        }
        else {
          x2 = splitPoints[j];
          previousX2 = previousSplitPoints[j];
        }

        this.colors[colorIndex % this.colors.length].setFill(multR, multG, multB);

        beginShape();
          vertex(previousX1, y);
          vertex(previousX2, y);
          vertex(x2, y + this.lineHeight);
          vertex(x1, y + this.lineHeight);
        endShape();

        x1 = x2;
        previousX1 = previousX2;
        colorIndex++;
      }
      previousSplitPoints = splitPoints;
    }
  }
}
class Clear implements Effect {
  public void setup() {
  }

  public void draw(float secs) {
    background(64, 64, 80);
    clear();
  }
}
class Color {
  public int r;
  public int g;
  public int b;
  public int a;

  Color(int r,int g,int b) {
    this.r = r;
    this.g = g;
    this.b = b;
    this.a = 255;
  }

  Color(int r,int g,int b, int a) {
    this.r = r;
    this.g = g;
    this.b = b;
    this.a = 255;
  }

  public void setFill() {
    fill((float)this.r, (float)this.g, (float)this.b, (float)this.a);
  }

  public void setFill(float multR, float multG, float multB) {
    fill(
      multR * (float)this.r, 
      multG * (float)this.g,
      multB * (float)this.b,
      (float)this.a
    );
  }

  public void setStroke() {
    stroke((float)this.r, (float)this.g, (float)this.b, (float)this.a);
  }
}
class Demoni {
  public PImage texture;
  float x;
  public float y;

  int mode = 0;

  Demoni() 
  {
    y = height / 2;
    x = -1024 * 2.2f;
    texture = loadImage("DEMONI.png");
  }

  float t;

  public void draw(float delta) {
    if (mode == 0 || mode == 2) {
      x += delta * 200;
    }

    if (mode == 1)  {
      t += delta;
      if (t > 3)
        mode = 2;
    }

    if (x > 200 && mode == 0) {
      mode = 1;
    }

    texture(texture);
    tint(255, 255);
    image(texture, x, y, 768, 384);   
  }  
}
interface Effect {
  public void setup();
  public void draw(float time);
}
class Effect2 implements Effect {
  public void setup() {
  }

  public void draw(float secs) {
    int d = 100;
    int p1 = d;
    int p2 = p1+d;
    int p3 = p2+d;
    int p4 = p3+d;
    
    background(0);
    translate(140, 0);
    
    // Draw gray box
    stroke(153, 0, 0);
    line(p3, p3, p2, p3);
    line(p2, p3, p2, p2);
    line(p2, p2, p3, p2);
    line(p3, p2, p3, p3);
    
    // Draw white points
    stroke(255, 200, 200);
    star(p1, p1, 30, 70, 5);
    star(p2, p2, 30, 50, 7);
    star(p3, p3, 20, 40, 9);
  }
  public void star(float x, float y, float radius1, float radius2, int npoints) {
    float angle = TWO_PI / npoints;
    float halfAngle = angle/2.0f;
    beginShape();
    for (float a = 0; a < TWO_PI; a += angle) {
      float sx = x + cos(a) * radius2;
      float sy = y + sin(a) * radius2;
      vertex(sx, sy);
      sx = x + cos(a+halfAngle) * radius1;
      sy = y + sin(a+halfAngle) * radius1;
      vertex(sx, sy);
    }
    endShape(CLOSE);
  }
}
class Ellipse implements Effect {
  public void setup() {
  }

  public void draw(float secs) {
    resetMatrix();
    translate(CANVAS_WIDTH/2.0f, CANVAS_HEIGHT/2.0f);
    scale(CANVAS_WIDTH/2.0f/ASPECT_RATIO, -CANVAS_HEIGHT/2.0f);
    // Clear the screen after previous frame.
    // If you comment this, you always draw on top the last frame,
    // which can lead to something interesting.
    clear();
    noStroke();
    fill(255);
    ellipse(0.f, 0.f, 1.0f, 1.0f);
  }
}
Mesh blob;
Mesh blobPulse;

Mesh fishDefault;
Mesh fishUpperFin;
Mesh fishLowerFin;
Mesh fishTail;
Mesh fishMouth;

Mesh seaStar;
Mesh seaStarPulse;

Mesh squid;
Mesh squidPulse;


class Fish {
  MeshSet meshSet;
  public float fins;
  public float tail;
  public float mouth;

  public float mode = -1;

  public float energy;

  public PImage blobTexture;
  public PImage fishTexture;
  public PImage seastarTexture;
  public PImage squidTexture;

  public Color fillColor;
  public Color strokeColor;

  public float speedX;
  public float speedY;
  public float initSpeedX;
  public float initSpeedY;
  public float x;
  public float y;

  public float seastarness;
  public float fishiness;
  public float squidness;
  public float blobness;

  public float phase;

  public void step(float sec, float secdiff) {
    x += speedX * secdiff;
    y += speedY * secdiff; 

    speedX *= 1 - 2.995f * secdiff;
    speedY *= 1 - 2.995f * secdiff;


    energy *= 1.0f - 0.95f * secdiff;

    fishiness = 0; 
    seastarness = 0; 
    squidness = 0;
    blobness = 0;
    if (mode < 0)
    {
      blobness = -mode;
      seastarness = 1 + mode;
    }
    else if (mode < 1)
    {
      seastarness = 1 - mode;
      fishiness = mode;
    }
    else if (mode <= 2) {
      fishiness = 2 - mode;
      squidness = mode - 1;
    }
    else {
      seastarness = mode - 2;
      squidness = mode - 2;
      fishiness = mode - 2;
    }

    meshSet.parameters[0] = fishiness;
    meshSet.parameters[1] = energy * fishiness;
    meshSet.parameters[2] = energy * fishiness;
    meshSet.parameters[3] = energy * fishiness;
    meshSet.parameters[4] = (sin(sec * 3 + phase) * 0.5f + 0.5f) * fishiness;
    meshSet.parameters[5] = seastarness;
    meshSet.parameters[6] = energy * seastarness;
    meshSet.parameters[7] = squidness;
    meshSet.parameters[8] = energy * squidness; 
    meshSet.parameters[9] = blobness;
    meshSet.parameters[10] = energy * blobness;
  }

  public void pulse() {
    energy = 1;
    speedX = initSpeedX;
    speedY = initSpeedY;
  }

  Fish() {
    meshSet = new MeshSet(11, 200);
    meshSet.setMesh(0, fishDefault, 0);
    meshSet.setMesh(1, fishUpperFin, 0);
    meshSet.setMesh(2, fishLowerFin, 0);
    meshSet.setMesh(3, fishTail, 0);
    meshSet.setMesh(4, fishMouth, 0);
    meshSet.setMesh(5, seaStar, 0.3f);
    meshSet.setMesh(6, seaStarPulse, 0.3f);
    meshSet.setMesh(7, squid, 0);
    meshSet.setMesh(8, squidPulse, 0);
    meshSet.setMesh(9, blob, 0);
    meshSet.setMesh(10, blobPulse, 0);

    meshSet.parameters[0] = 1;
    meshSet.parameters[1] = 0;
    meshSet.parameters[2] = 0;
    meshSet.parameters[3] = 0;
    meshSet.parameters[4] = 0;
    meshSet.parameters[5] = 0;
    meshSet.parameters[6] = 0;
    meshSet.parameters[7] = 0;
    meshSet.parameters[8] = 0;
    meshSet.parameters[9] = 0;
    meshSet.parameters[10] = 0;
  }

  public void draw() {
    pushMatrix();

    translate(x, y);

    rotate(atan2(initSpeedX, initSpeedY));

    Mesh m = meshSet.getMesh();

    scale(height / 3);

    if (blobness > 0)
      m.renderWithImage(blobTexture, 255);
    if (seastarness > 0)
      m.renderWithImage(seastarTexture, (int)(blobness > 0 ? (seastarness * 255) : 255));
    if (fishiness > 0)
      m.renderWithImage(fishTexture, (int)(seastarness > 0 ? (fishiness * 255) : 255));
    if (squidness > 0)
      m.renderWithImage(squidTexture, (int)(squidness * 255));


    float eye1X 
      = 0.7f * fishiness
      + -0.2f * seastarness
      + 0.7f * squidness;
    float eye1Y 
      = -0.1f * fishiness
      + 0 * seastarness
      + -0.15f * squidness;
      ;
    float eye2X 
      = 0.7f * fishiness
      + 0.2f * seastarness
      + 0.7f * squidness;
    float eye2Y 
      = -0.1f * fishiness
      + 0 * seastarness
      + 0.15f * squidness;

    fill (0, 255 * (1.0f - blobness));
    ellipse(eye1X, eye1Y, 0.13f, 0.13f);
    fill (255, 255 * (1.0f - blobness));
    ellipse(eye1X + 0.04f, eye1Y - 0.04f, 0.04f, 0.04f);

    fill (0, 255 * (1.0f - blobness));
    ellipse(eye2X, eye2Y, 0.13f, 0.13f);
    fill (255, 255 * (1.0f - blobness));
    ellipse(eye2X + 0.04f, eye2Y - 0.04f, 0.04f, 0.04f);

    popMatrix();
  }
}

class EvolveBlob implements Effect {
  Fish f;
  boolean moveout; 
  PImage blobTexture;
  PImage fishTexture;
  PImage seastarTexture;
  PImage squidTexture;
  int lastPulse;

  public void setup() {
    fishDefault = new Mesh(17);
    blob = new Mesh(8);
    blobPulse = new Mesh(8);
    fishUpperFin = new Mesh(17);
    fishLowerFin = new Mesh(17);
    fishTail = new Mesh(17);
    fishMouth = new Mesh(17);

    blob
      .v(1, 0)
      .v(0, -0.4f)
      .v(-0.5f, -0.5f)
      .v(-1, -0.5f)
      .v(-0.9f, -0.3f)
      .v(-0.7f, 0)
      .v(-0.4f, 0.6f)
      .v(0.8f, 0.8f);

    blobPulse
      .v(-0.5f, -0.2f)
      .v(-0.3f, 0.3f)
      .v(-0.3f, 0.3f)
      .v(0.1f, 0.5f)
      .v(0.1f, 0.5f)
      .v(0.3f, 0.2f)
      .v(-0.2f, 0.2f)
      .v(-0.2f, 0.1f);

    fishDefault
      .v(1,    0.1f)    //Lower lip
      .v(0.96f, 0)      //Mouth
      .v(1,    -0.1f)   //Upper lip
      .v(0.6f,  -0.28f)
      .v(0.2f,  -0.3f) // Upper fin begin
      .v(0,    -0.35f) // Upper fin top
      .v(-0.3f, -0.5f) // Upper fin top
      .v(-0.3f, -0.15f) // Upper fin end
      .v(-0.3f, -0.2f) // Tail begin
      .v(-0.35f, -0.17f) // Tail upper
      .v(-0.4f,  0) // Tail middle
      .v(-0.35f,  0.17f) // Tail middle
      .v(-0.3f,  0.2f) // Tail end
      .v(-0.3f,  0.15f) // Lower fin begin
      .v(-0.3f,  0.5f) // Lower fin bottom
      .v(0.2f,  0.3f) // Lower fin end
      .v(0.6f,  0.28f); // Lower fin end

    fishUpperFin
      .v(0, 0)
      .v(0, 0)
      .v(0, 0)
      .v(0, 0)
      .v(0, 0)
      .v(-0.05f, -0.4f)
      .v(-0.03f, -0.5f)
      .v(0, 0)
      .v(0, 0)
      .v(0, 0)
      .v(0, 0)
      .v(0, 0)
      .v(0, 0)
      .v(0, 0)
      .v(0, 0)
      .v(0, 0)
      .v(0, 0);

    fishLowerFin
      .v(0, 0)
      .v(0, 0)
      .v(0, 0)
      .v(0, 0)
      .v(0, 0)
      .v(0, 0)
      .v(0, 0)
      .v(0, 0)
      .v(0, 0)
      .v(0, 0)
      .v(0, 0)
      .v(0, 0)
      .v(0, 0)
      .v(0, 0)
      .v(-0.05f, 0.5f)
      .v(0, 0)
      .v(0, 0);

    fishTail
      .v(0, 0)
      .v(0, 0)
      .v(0, 0)
      .v(0, 0)
      .v(0, 0)
      .v(0, 0)
      .v(0, 0)
      .v(0, 0)
      .v(-0.25f, 0)
      .v(-0.65f, -0.35f)
      .v(-0.25f, 0)
      .v(-0.65f, 0.35f)
      .v(-0.25f, 0)
      .v(0, 0)
      .v(0, 0)
      .v(0, 0)
      .v(0, 0);

    fishMouth
      .v(0, 0.05f)
      .v(-0.2f, 0)
      .v(0, -0.05f)
      .v(0, 0)
      .v(0, 0)
      .v(0, 0)
      .v(0, 0)
      .v(0, 0)
      .v(0, 0)
      .v(0, 0)
      .v(0, 0)
      .v(0, 0)
      .v(0, 0)
      .v(0, 0)
      .v(0, 0)
      .v(0, 0)
      .v(0, 0);

    seaStar = new Mesh(10);

    for (int i = 0; i < 10;) {
      seaStar.v(cos(-TWO_PI * i / 10.0f), sin (-TWO_PI * i / 10.0f));
      ++i;
      seaStar.v(cos(-TWO_PI * i / 10.0f) * 0.3f, sin (-TWO_PI * i / 10.0f) * 0.3f);
      ++i;
    }

    seaStarPulse = new Mesh(10);

    for (int i = 0; i < 10;) {
      seaStarPulse.v(0, 0);
      ++i;
      seaStarPulse.v(cos(-TWO_PI * i / 10.0f) * 0.3f, sin (-TWO_PI * i / 10.0f) * 0.3f);
      ++i;
    }

    squid = new Mesh(50);
    squid
      .v(1,    0) 
      .v(0.98f, -0.02f)
      .v(0.96f, -0.015f)
      .v(0.6f,  -0.515f)
      .v(0.15f, -0.2f)
      .v(0,    -0.8f)
      .v(0,    -0.8f)
      .v(0,    -0.8f)
      .v(0,    -0.8f)
      .v(0,    -0.8f)

      .v(-0.95f,-0.9f)

      //.v(-1,   -0.95)
      //.v(-0.2, -0.90)
      .v(-1,   -0.85f)
      .v(-0.2f, -0.70f)
      .v(-0.1f, -0.65f)
      .v(-0.2f, -0.60f)
      .v(-1,   -0.55f)

      .v(-0.2f, -0.40f)
      .v(-0.1f, -0.35f)
      .v(-0.2f, -0.30f)
      .v(-1,  -0.20f)
      .v(-0.2f, -0.10f)
      .v(-0.1f, -0.05f)
      .v(-0.2f, -0.00f)
      .v(-1,  0.05f)
      .v(-0.2f, 0.10f)
      .v(-0.1f, 0.15f)

      .v(-0.2f, 0.20f)
      .v(-1,  0.25f)
      .v(-0.2f, 0.30f)
      .v(-0.1f, 0.35f)
      .v(-0.2f, 0.40f)
      .v(-1,  0.45f)
      .v(-0.2f, 0.50f)
      .v(-0.1f, 0.55f)
      .v(-0.2f, 0.60f)
      .v(-1,  0.65f)

      .v(-0.2f, 0.70f)
      .v(-0.1f, 0.75f)
      .v(-0.2f, 0.80f)
      .v(-1,  0.85f)
      .v(-0.95f,0.9f)

      .v(0,    0.8f)

      .v(0,    0.8f)
      .v(0,    0.8f)
      .v(0,    0.8f)
      .v(0,    0.8f)

      .v(0.15f, 0.2f)
      .v(0.6f,  0.515f)
      .v(0.96f, 0.015f)
      .v(0.98f, 0.02f)
      ;

    squidPulse = new Mesh(50);
    squidPulse
      .v(0,    0) 
      .v(0,    0) 
      .v(0,    0) 
      .v(0,    -0.3f) 
      .v(0,    0) 

      .v(0.3f,    0.1f) 
      .v(0.3f,    0.1f) 
      .v(0.3f,    0.1f) 
      .v(0.3f,    0.1f) 
      .v(0.3f,    0.1f) 

      .v(0,    0) 

      .v(0,    0) 
      .v(0.2f,  0)
      .v(0.2f,  0)
      .v(0.2f,  0)
      .v(0,    0) 

      .v(0.2f,  0)
      .v(0.2f,  0)
      .v(0.2f,  0)
      .v(0,    0) 
      .v(0.2f,  0)
      .v(0.2f,  0)
      .v(0.2f,  0)
      .v(0,    0) 

      .v(0.2f,  0)
      .v(0.2f,  0)
      .v(0.2f,  0)
      .v(0,    0) 

      .v(0.2f,  0)
      .v(0.2f,  0)
      .v(0.2f,  0)
      .v(0,    0) 
      .v(0.2f,  0)
      .v(0.2f,  0)
      .v(0.2f,  0)
      .v(0,    0) 

      .v(0.2f,  0)
      .v(0.2f,  0)
      .v(0.2f,  0)
      .v(0,    0) 
      .v(0, 0)

      .v(0.3f, 0.1f)
      .v(0.3f, 0.1f)
      .v(0.3f, 0.1f)
      .v(0.3f, 0.1f)
      .v(0.3f, 0.1f)

      .v(0,    0)
      .v(0,  0.3f)
      .v(0,    0)
      .v(0,    0);


    lastPulse = 0;
    f = new Fish();
    f.phase = 1;
    f.strokeColor = new Color(0,94,128);

    this.blobTexture = loadImage("renderclouds.jpg");
    this.fishTexture = loadImage("ananas.jpg");
    this.seastarTexture = loadImage("seastar.jpg");
    this.squidTexture = loadImage("mustekala1.jpg");

    f.blobTexture = this.blobTexture;
    f.fishTexture = this.fishTexture;
    f.seastarTexture = this.seastarTexture;
    f.squidTexture = this.squidTexture;

    f.x = -width;
    f.y = -height;
    //f.x = width / 2;
    //f.x = height / 2;
    f.speedX = 0;
    f.speedY = 0;
    f.initSpeedX = width / 3;
    f.initSpeedY = height / 3;
    lastSec = 0;
  }

  float lastSec;

  public void pulse() {
    if (moveout) {
    f.initSpeedX = 150;
    f.initSpeedY = 150;
    }
    else {
      float distx = width / 2 - f.x;
      float disty = height / 2 - f.y;
      distx *= distx;
      disty *= disty;
      if (distx + disty > 12000) {
        f.initSpeedX = width / 2 - f.x;
        f.initSpeedY = height / 2 - f.y;
        float l = 400 / sqrt(f.initSpeedX * f.initSpeedX + f.initSpeedY * f.initSpeedY);
        f.initSpeedX *= l;
        f.initSpeedY *= l;
      }
    }
    f.pulse();
  }

  public void draw(float secs) {
    resetMatrix();


    float delta = secs - lastSec;
    if (lastSec == 0)
      delta = 0;
    f.step(secs, delta);
    //f.mode=-1;
    //f.mode=(0.5 + 0.5* sin(secs/10)) * 3.0 - 1.0;
    lastSec = secs;

    f.draw();
  }
}
class Greets {
  int string = 0;
  int mode = 0;
  float alpha = 0;
  float wait = 0;

  Greets() 
  {
  }

  float t;

  public void draw(float delta) {
    textSize(32);
    fill(255, 255, 255, alpha * 255);
    stroke(0, 0, 0, alpha * 255);

    textSize(40);
    switch (string) {
      case 0:
        text("Lemon Giraffe", 200, 200);
        text("sends greetz to", 200, 250);
        text("DOT", 200, 300);
        break;
      case 1:
        text("and", 200, 250);
        text("Peisik", 200, 300);
        break;
      case 2:
        text("also,", 200, 250);
        text("petrim", 200, 300);
        break;

      case 3:
        text("And our fish", 200, 200);
        text("sends greetz to", 200, 250);
        text("Napababa", 200, 300);
        break;
    }
    if (mode == 0) {
      alpha += delta * 0.4f;
      if (alpha > 1) {
        alpha = 1;
        mode = 2;
      }
    }
    if (mode == 1) {
      wait += delta;
      if (wait > 8)
        mode = 2;
    }

    if (mode == 2) {
      alpha -= delta * 0.4f;
      if (alpha < 0) {
        string++;
        wait = 0;
        mode = 0;
      }
    }
  }  
}
class Group {
  public PImage texture;
  float x;
  public float y;

  int mode = 0;

  Group() 
  {
    y = height / 5;
    x = -512;
    texture = loadImage("GROUP.png");
  }

  float t;

  public void draw(float delta) {
    if (mode == 0 || mode == 2) {
      x += delta * 150;
    }

    if (mode == 1)  {
      t += delta;
      if (t > 3)
        mode = 2;
    }

    if (x > 200 && mode == 0) {
      mode = 1;
    }

    texture(texture);
    tint(255, 255);
    image(texture, x, y, 768, 384);   
  }  
}
public float cardinal(float prev, float cur, float next, float nextnext, float t, float c) {
  float t2 = t * t;
  float t3 = t * t2;
  float result = 0;

  float m0 = (next - prev)  * (1.0f - c) / 2.0f;
  float m1 = (nextnext - cur)  * (1.0f - c) / 2.0f;


  result += (2*t3 - 3*t2 + 1) * cur;
  result += (t3 - 2*t2 + t) * m0;
  result += (-2 * t3 + 3*t2) * next;
  result += (t3 - t2) * m1;

  return result;
}

public Mesh interpolate(Mesh input, int num, float c) {
  Mesh result = new Mesh(num);

  float tDelta = (float)input.x.length / (float)num;
  float t = 0;
  int inputIndex = 0;

  for (int i = 0; i < num; ++i) {
    while (t > 1.0f) {
      t -= 1.0f;
      ++inputIndex;
    }

    int prevIndex = inputIndex - 1;
    int nextIndex = inputIndex + 1;
    int nextNextIndex = inputIndex + 2;

    if (prevIndex < 0) prevIndex = input.x.length - 1;
    if (nextIndex >= input.x.length) nextIndex -= input.x.length;
    if (nextNextIndex >= input.x.length) nextNextIndex -= input.x.length;

    result.x[i] = cardinal(
      input.x[prevIndex],
      input.x[inputIndex],
      input.x[nextIndex],
      input.x[nextNextIndex],
      t,
      c);

    result.y[i] = cardinal(
      input.y[prevIndex],
      input.y[inputIndex],
      input.y[nextIndex],
      input.y[nextNextIndex],
      t,
      c);

    t += tDelta;
  }

  return result;
}

 class JatkuvatKuplat {
  public PImage texture; 
  Kupla k[]; 
  boolean isStopped = false;
  public void stop() {
    isStopped = true;
  }

  public void setup() {
    k = new Kupla[20];
    this.texture = loadImage("kupla.png");
    for (int i = 0; i < k.length; i++){
      k[i] = new Kupla();
      k[i].texture = this.texture;
      k[i].size = random(width / 20) + 10;
        do {
        k[i].x = random(-sqrt(width / 2), sqrt(width / 2));
        k[i].x *= abs(k[i].x);
      k[i].x += k[i].x + width / 2;
      } while (k[i].x > width / 3 && k[i].x < width / 3 * 2);
      k[i].lift /= 5;
    }
  }

  public void draw(float secs) {
    strokeWeight(1);
    resetMatrix();    
    for (int i = 0; i < k.length; i++){
        k[i].draw(secs);
        if (k[i].y < -20) {
          if (!isStopped) 
          {
          k[i] = new Kupla();
          k[i].texture = this.texture;
          k[i].y = height;
          k[i].size = random(width / 20) + 10;

          do {
          k[i].x = random(-sqrt(width / 2), sqrt(width / 2));
          k[i].x *= abs(k[i].x);
          k[i].x += k[i].x + width / 2;
          } while (k[i].x > width / 3 && k[i].x < width / 3 * 2);

          k[i].lift /= 5;
          }
        }        
    }
  }
}
class Kupla {
  public PImage texture;
  float x;
  public float y;
  float size;
  float mod;
  float lift;

  Kupla() {
    y = height + 50+ random(height) * 2; 
    size = random(width / 3); 
    x = random(width) - size / 2;
    mod = random(width / 300);
    lift = height/(random(height / 35)+200);
  }

  public void draw(float secs) {
    tint(255, 127);  
    texture(texture);
    x = x + sin(secs * 0.5f)*mod;
    y = y - lift * secs;
    image(texture, x, y, size, size);   
  }  
}
 
 class Kuplat {
  public PImage texture; 
  Kupla k[]; 

  public void setup() {
    k = new Kupla[200];
    this.texture = loadImage("kupla.png");
    for (int i = 0; i < k.length; i++){
      k[i] = new Kupla();
      k[i].texture = this.texture;
    }
  }

  public void draw(float secs) {
    strokeWeight(1);
    resetMatrix();    
    for (int i = 0; i < k.length; i++){
        if (k[i].y > -20) {
          k[i].draw(secs);
          //k[i] = null;
          //k[i] = new Kupla();
          //k[i].texture = this.texture;
          //k[i].y = height;
        }        
    }
  }
}
class Mesh {
  public float x[], y[];
  int curCoord;

  Mesh (int size) {
    this.x = new float[size];
    this.y = new float[size];
    curCoord = 0;
  }

  public Mesh v(float x, float y) {
    this.x[curCoord] = x;
    this.y[curCoord] = y;
    curCoord++;
    return this;
  }

  public void renderWithImage(PImage image, int opacity) {
    float uFactor = image.width * 0.5f;
    float vFactor = image.height * 0.5f;
    noStroke();
    noFill();
    beginShape();
    texture(image);
    tint(255, opacity);
    for (int i = 0; i < x.length; ++i) {
      vertex(
        x[i],
        y[i],
        (1 + x[i]) * uFactor,
        (1 + y[i]) * vFactor);
    }
    endShape();
  }
}


class MeshSet {
  Mesh meshes[];
  float parameters[];

  int meshSize;

  MeshSet(int numMeshes, int meshSize) {
    meshes = new Mesh[numMeshes];
    parameters = new float[numMeshes];
    this.meshSize = meshSize;
  }

  public void setMesh(int index, Mesh mesh, float c) {
    if (meshSize != mesh.x.length) {
      meshes[index] = interpolate(mesh, meshSize, c);
    }
    else {
      meshes[index] = mesh;
    }
  }

  public Mesh getMesh() {
    Mesh result = new Mesh(meshSize);
    for (int i = 0; i < meshes.length; ++i) {
      for (int j = 0; j < meshSize; ++j) {
        result.x[j] += meshes[i].x[j] * parameters[i];
        result.y[j] += meshes[i].y[j] * parameters[i];
      }
    }
    return result;
  }
}

 class Puu { 
  float leftr = 0.85f;
  float rightr = 0.95f; 
  public void setup() {
  }

  public void draw(float secs, float x, float y, float r, float ang, float left, float right, float mult, float alpha) {
    strokeWeight(6);
    
    resetMatrix();

    stroke(50 * (1.0f-mult), 255*mult, 92*mult, alpha * 255);
    line(x,height,x,height-y); //varrenpidennys
    tree(x, y, r, 0, secs, ang, left, right, mult, alpha);

  }
  public void tree(float x, float y, float r, int end, float secs, float ang, float left, float right, float mult, float alpha) {
    if (end > 9 || r <= 0){ 
      return; 
    }
    stroke(50 * (1.0f - mult), 255*mult, 92*mult, alpha * 255);
    strokeWeight(6 - end/1.8f);
    float asdf = cos(PI/2 + PI * secs * (6.0f / 8.0f) ) / 20;
    //vasen
    float x2 = x + (r * cos(PI/4 + ((PI/2)*ang)));
    float y2 = y + (r * sin(PI/4 + ((PI/2)*ang)));
    line(x,height-y,x2,height-y2);
    tree(x2, y2, r*leftr, end+1, secs, ang+left+asdf, left, right, mult * 0.90f, alpha);
    //oikea      
    tree(x2, y2, r*rightr, end+1, secs, ang+right+asdf, left, right, mult * 0.85f, alpha);
  }
}
 class Ruusu implements Effect {
   float alpha = 0;
  float r = height/12;  
  float leftr = 0.85f;
  float rightr = 0.95f; 
  public void setup() {
  }

  public void draw(float secs) {
    float x = width/2;
    float y = height/2.7f;

    //background(0);
    resetMatrix();
    strokeWeight(2.0f);
    tree(x, y, r, 0, secs, 0.5f, 1);
  }
  public void tree(float x, float y, float r, int end, float secs, float ang, float mult) {
    stroke(255 * mult, 32 * (1.0f - mult), 64 * mult, 255 * alpha);
    if (end > 9 || r <= 0){ 
      return; 
    }
    float asdf = sin(secs);
    //vasen
    float x2 = x + (r * cos(PI/4 + ((PI/2)*ang)));
    float y2 = y + (r * sin(PI/4 + ((PI/2)*ang)));
    line(x,height-y,x2,height-y2);
    tree(x2, y2, r*leftr, end+1, secs, ang+0.2f+asdf, mult * 0.95f);
    //oikea      
    tree(x2, y2, r*rightr, end+1, secs, ang-0.2f+asdf, mult*0.86f);
  }
}
 class Verkko implements Effect {
  int div1 = 1;
  int div2 = 10;
  float r = height/12;  
  public void setup() {
  }

  public void draw(float secs) {
    strokeWeight(1);
    float x = width/2;
    float y = 0; 

    background(0);
    
    stroke(0, 255, 0);
    
    tree(x, y, r, 12, secs);

  }
  public void tree(float x, float y, float r, int end, float secs) {
    if (end < 1){ 
      return; 
    }
    float x2 = x + (r * cos(PI/4 + ((PI/2)/div1)));
    float y2 = y + (r * sin(PI/4 + ((PI/2)/div1)));
    line(x,height-y,x2,height-y2);
    tree(x2, y2, r, end-1, secs%20);    
    x2 = x + (r * cos(PI/4 + ((PI/2)/div2-(end/secs))));
    y2 = y + (r * sin(PI/4 + ((PI/2)/div2-(end/secs))));
    line(x,height-y,x2,height-y2);    
    tree(x2, y2, r, end-1, secs%35);
  }
}
  static public void main(String[] passedArgs) {
    String[] appletArgs = new String[] { "demo" };
    if (passedArgs != null) {
      PApplet.main(concat(appletArgs, passedArgs));
    } else {
      PApplet.main(appletArgs);
    }
  }
}
