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

import ddf.minim.*; 
import ddf.minim.analysis.*; 
import processing.sound.*; 
import ddf.minim.*; 
import ddf.minim.*; 
import ddf.minim.analysis.*; 

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




Minim minim;
AudioPlayer s;
ddf.minim.analysis.FFT f;
  
processing.sound.SoundFile sample;  
processing.sound.SoundFile sample2;
processing.sound.SoundFile sample3;
processing.sound.SoundFile sample4;
processing.sound.Amplitude rms;
PImage img;
PShape spinner;


float zPosition;
float timePrev;
int stage;
int demoCounter;
float angle;

ArrayList<demoBase> Demos;

public void setup() {
  
  demoCounter = 0;
  minim = new Minim(this);
  //s = minim.loadFile("Hypp_fractal_-_09_-_Vertigo.mp3");
  s = minim.loadFile("Furious Freak.mp3");
  f = new ddf.minim.analysis.FFT(s.bufferSize(), s.sampleRate());
  
  
  PImage iJ = loadImage("Juge.JPG");
  PImage iH = loadImage("Hai.jpg");
  PImage iT = loadImage("Timo.jpg");
  PImage iM = loadImage("Max.jpg");
  
  
  sample = new processing.sound.SoundFile(this, "Furious Freak.mp3");
  sample2 = new processing.sound.SoundFile(this, "Furious Freak.mp3");
  sample3 = new processing.sound.SoundFile(this, "Furious Freak.mp3");
  sample4 = new processing.sound.SoundFile(this, "Furious Freak.mp3");
  
  
  PShape spinnuri = loadShape("helicopter_2.obj");
  PShape spinner = loadShape("fidget_spinner_final123.obj");
  
  rms = new processing.sound.Amplitude(this);
  //img = loadImage("texture.png");
  spinner = loadShape("fidget_spinner_final123.obj");
    
  zPosition = 0;
  stage = 0;
  timePrev = 0;
  
  Demos = new ArrayList<demoBase>();
  Demos.add(new lineDemo(sample3));
  Demos.add(new spinnerDemo(sample, rms, img, spinner));
  Demos.add(new TravercingBall(s, f));
  Demos.add(new copterDemo(sample4, spinnuri, spinner));
  Demos.add(new Presentation(iJ, iH, iT, iM, sample2));
  
}
public void draw() {
  
  float time = millis()/1000.0f;
  
  switch(stage){
    case 0:   
      drawStuff();
      if(time - timePrev > 2) {
        stage = 1;
        timePrev = time;
      }
      break;
    case 1:  
      drawStuff();
      zPosition += 10;
      if(zPosition > 700) {
        stage = 2;
        timePrev = time;
      }
      break;
    case 2:
       if(Demos.size() <= demoCounter) {
         stage = 5;
       }
       else {
         demoBase demo = Demos.get(demoCounter);
         demo.playDemo();
         if(demo.isOver()) {
          stage = 3;
          timePrev = time;
          demoCounter++;
        }
      }
      break;
   case 3:  
      drawStuff();
      zPosition -= 10;
       if(zPosition < 300) {
        stage = 4;
        timePrev = time;
        angle = 0;
      }
      break;
    case 4:  
    drawStuff();
    angle += 3;
     if(angle > 89) {
      angle = 90;
      stage = 1;
      timePrev = time;
    }
    break;
    case 5: 
      exit();
    break;
  }
}

public void drawStuff() {
  pushMatrix();
  background(0);
  translate(width/2, height/2, zPosition); 
  rotateY(radians(angle));
  noFill();
  stroke(255);
  box(250);
  popMatrix();
}
class Anomaly 
{

  float yCoord;
  float xCoord;
  float zCoord;
  
  float xVelocity;
  float yVelocity;
  
  float yGround;
  
  float angle;
  public Anomaly(float xCoord) {
    yGround = height  - 200;
    
    this.xCoord = xCoord;
    yCoord = height  - 200;
    
    zCoord = -400;
    angle = 0;
  }
  
  public void bounce(){
    if(yVelocity > -30)
      yVelocity -=  random(15, 25);
  }
  
  public void draw(int r, int g, int b, float high) {
   yCoord += yVelocity; 
   zCoord -= random(-10, 10) / 10;
   
   if(yCoord > yGround) {
     yCoord = yGround;
     yVelocity = 0;
   }
   
   pushMatrix();
   translate(xCoord, yCoord, zCoord);
   fill(0,0,0,255);
   rotateX(radians(angle));
   strokeWeight(1);
   stroke(r, g, b);
   //noFill();
   //stroke(255);
   sphere(300 + high);
   popMatrix();
   
   yVelocity += 8;
   angle += 8;
   }
}

class copterDemo extends demoBase {
  
  
  PShape s;
  PShape spinner;

  float startTime;
  SoundFile sample;
  float rotation = -90;
  float rotAcc = 0.5f;
  float copterZ = -210;
  float copAcc = 0.2f;
  
  public copterDemo(SoundFile sample, PShape s, PShape spinner) {
    
    startTime = -1;
    this.sample = sample;
    this.s = s;
    this.spinner = spinner;
  }
  public void playDemo() {
    if(startTime == -1) {
      startTime = millis()/1000.0f;
      sample.loop();
    }
    
  background(0);
  lights();  
  translate(width*0.5f,height*0.5f,0);
  
  //rotateY(-PI/ 4);
  rotateX(-0.1f*PI);
  
  // MEH NEED timeDelta
  if(rotation >= -395){
    rotateY(radians(rotation));
    rotation-=rotAcc;  
    rotAcc+=0.005f;
  }
  else {
    rotateY(radians(rotation));
    copterZ+=copAcc;
    copAcc+=0.1f;
  }
    
  translate(0,0,copterZ);
  drawChoppa();
  
  }
  
  public void drawChoppa () {
    
  float t = millis()/1000.0f;
  pushMatrix();
  translate(0,100,0);
  scale(15);
  rotateZ(PI);
  shape(s, 0, 0);
  popMatrix();
  
  pushMatrix();
  translate(0,-15,40);
  scale(20);
  rotateX(PI);
  rotateY(5*PI*t);
  shape(spinner, 0, 0);
  popMatrix();
  
  pushMatrix();
  translate(10,-10,-210);
  scale(8);
  rotateZ(PI/2); 
  rotateY(5*PI*t);
  shape(spinner, 0, 0);
  popMatrix();
  }
  public boolean isOver(){
    boolean result = false;
    if(millis()/1000.0f - startTime > 7) {
      sample.stop();
      result = true;
    }
    return result;
  }
}
abstract class demoBase
{
  public abstract void playDemo();
  
  public abstract boolean isOver();
}
class MistGenerator {
  float xPos;
  float yPos;
  float zPos;
  
  ArrayList<mistParticle> particleList;
  
  public MistGenerator(float x, float y, float z) {
    xPos = x;
    yPos = y;
    zPos = z;
    particleList = new ArrayList<mistParticle>();
    makeMist();
  }
  
  public void makeMist() {
    for(int i = 0; i < 25; i ++){
      particleList.add(new mistParticle(xPos + random(-400, 400) , yPos + random(-300, 200) , zPos));
    }
  }
  
  public void spawnNewParticle() {
    particleList.add(new mistParticle(xPos + random(-400, 400) , yPos + random(-300, 200) , zPos));
  }
  public void draw(float r, float g, float b){
  float mult = randomGaussian();
  r = r * mult;
  g = g * mult;
  b = b * mult;
    
   for(int i = 0; i < particleList.size(); i ++){
     mistParticle p = particleList.get(i);
     p.draw(r, g, b);
     
     if(!p.isAlive()){
       particleList.remove(i);
       spawnNewParticle();
     }
   }
  }
}

class mistParticle{

  float xPos;
  float yPos;
  float zPos;
  
  float lifespan;
  float treshold;
  float sphereSize;
  
  boolean isBooming;
  boolean isDecaying;
  public mistParticle(float x, float y, float z) {
    this.xPos = x;
    this.yPos = y;
    this.zPos = z;
    
    zPos = random(-1000, -100);
    sphereSize = random(5, 10);
    
    isBooming = true;
    isDecaying = false;
    lifespan = random(10, 60);
    treshold = random(100, 150);
  }
  
  public void draw(float r, float g, float b){ 
    
    
    pushMatrix();
    fill(r, g, b ,lifespan);
    noStroke();
    translate(xPos, yPos, zPos);
    sphere(sphereSize);
    popMatrix();
    
    uptadePosition();
  }
  
  public void uptadePosition(){
    if(isBooming){
      if(lifespan < treshold){
        lifespan += random(0.1f, 0.8f);
      }
      else {
        isDecaying = true;
        isBooming = false;
      }
    }
    else {
      lifespan -= random(0.2f, 0.4f);
    }
    
    xPos += random(-0.1f, 0.1f);
    yPos += random(-0.1f, 0.1f);
    zPos += 10;
    if(zPos > 100){
      zPos = -1000;
    }
  }
  
  public boolean isAlive(){
    boolean result = true;
    if(lifespan < 0){
      result = false;
    }
    
    return result;
  }
}
class Presentation extends demoBase {
 
  PShape rectangle;
  PImage imgJugge;
  PImage imgHai;
  PImage imgMax;
  PImage imgTimo;
  PImage img;
  
  float angle;
  boolean large = false;
  float timeDelta = 0;
  float timePrev = 0;
  float startTime = -1;
  
  SoundFile sample;
  String s;
  public Presentation(PImage j, PImage h,PImage m,PImage t, SoundFile sample) {
    imgJugge = j;
    imgHai = h;
    imgMax = m;
    imgTimo = t;
    
    angle = 0;
    s = "";
    rectangle = createShape(SPHERE,250);
    rectangle.setTexture(img);
    
    this.sample = sample;
  }
 
  public void playDemo() {
    background(66,240,240);
    if(startTime == -1) {
      sample.loop();
      startTime = millis()/1000.0f;
    }
    getFace();
    float time = millis()/1000.0f;
    timeDelta = time - timePrev;
  if(timeDelta > 0.20f) {
      timePrev = time;
      
      if(large) {
        background(66,241,161);
        noStroke();
        fill(255);
        rectangle = createShape(SPHERE,250);
        rectangle.setTexture(img);
        large = false;
      }
      else{
        background(66,240,240);
        noStroke();
        fill(255);
        rectangle = createShape(SPHERE,450);
        rectangle.setTexture(img);
        large = true;
      }
    }
    else {
      if(large) {
        pushMatrix();
        background(66,241,161);
        noStroke();
        fill(255);
        rectangle = createShape(SPHERE,250);
        rectangle.setTexture(img);
        popMatrix();
      }
      else{
        pushMatrix();
        background(66,240,240);
        noStroke();
        fill(255);
        rectangle = createShape(SPHERE,450);
        rectangle.setTexture(img);
        popMatrix();
      }
    }
    
    //translate(width/2,height/2);
    pushMatrix();
    translate(width/2,height/2);
    rotateY(radians(angle));
    rotateX(radians(angle));
    shape(rectangle);
    popMatrix();
    
    pushMatrix();
    translate(10,10,0);
    noFill();
    stroke(255);
    sphere(280);
    popMatrix();
    
    pushMatrix();
    translate(80,height - 40,0);
    noFill();
    stroke(255);
    sphere(150);
    popMatrix();
    
    pushMatrix();
    textSize(28);
    fill(0, 102, 153);
    text(s, width - 500, 150); 
    text("song: Furious Freak", width - 300, height - 150); 
    popMatrix();

    angle++;
  }
  
  public void getFace() {
    float t = millis()/1000.0f - startTime;
    if(t < 4) {
      s = "HaiTech"; 
      img = imgHai;
    }
    else if(t < 8) {
      s = "Max";
      img = imgTimo;
    }
    else if(t < 13) {
      s = "Jidoor";
      img = imgMax;  
    }
    else if(t < 16) {
      s = "Juge";
      img = imgJugge;
    }
  }
  public boolean isOver() {
    boolean result = false;
    if(millis()/1000.0f - startTime > 19) {
      sample.stop();
      result = true;
    }
    return result;
  }
}
abstract class demo 
{
  public demo() {
  }
  
  public void playDemo(){
  }
}
class Road{

  float yCoord;
  float xCoord;
  float zCoord;
  
  ArrayList<roadStrip> stripList;
  MistGenerator mistGenerator1;
  MistGenerator mistGenerator2;
  MistGenerator mistGenerator3;
  public Road() {
    zCoord = -1000;
    
    mistGenerator1 = new MistGenerator(100, 100, -500);
    mistGenerator2 = new MistGenerator(width - 100, 100, -500);
    mistGenerator3 = new MistGenerator(width/2, 100, -500);
    
    stripList = new ArrayList<roadStrip>();
    stripList.add(new roadStrip(-1 * 500));
    stripList.add(new roadStrip(-1 * 1000));
    stripList.add(new roadStrip(-1500));
  }
  
  public void update(int r, int g, int b) {
    if(stripList.size() < 3) {
      stripList.add(new roadStrip(-1000));
    }
    
    mistGenerator1.draw(r, g, b);
    mistGenerator2.draw(r, g, b);
    mistGenerator3.draw(r, g, b);
    
    for(int i = 0; i < stripList.size(); i++) {
      roadStrip road = stripList.get(i);
      road.update(r, g, b);
    }
  }
}
class roadStrip {
  float yCoord;
  float xCoord;
  float zCoord;
  
  public roadStrip(float z) {
    zCoord = z;
  }
  
  public void update(int r, int g, int b) {
    pushMatrix();
    stroke(10+r, 10+g, 10+b, zCoord * -1);
    strokeWeight(10);
    line(-1500, height - 100, 100 + zCoord, width + 1500, height - 50, zCoord);
    popMatrix();
    
    zCoord += 10;
    if(zCoord > 100){
      zCoord = -1500;
    }
  }
}



class spinnerDemo extends demoBase {
  
  processing.sound.SoundFile sample;
  processing.sound.Amplitude rms;
  
  // Declare a scaling factor
  float scale=5;
  
  // Declare a smooth factor
  float smooth_factor=0.25f;
  
  // Used for smoothing
  float sum;
  
  PShape spinner;
  
  float startTime = 30;
  
  float demoStartTime = -1;
  float spinSpeed = 2.66f*PI;
  PImage img;

  
  boolean done;
  
  public spinnerDemo(SoundFile sample, Amplitude rms, PImage img, PShape spinner) {    
    this.sample = sample;
    this.rms = rms;
    this.img = img;
    this.spinner = spinner;
  }
  
  public void playDemo() {
    
  if(demoStartTime == -1){
    demoStartTime = (millis()/1000.0f);   
    sample.loop();
    sample.jump(startTime);
    // Create and patch the rms tracker
    rms.input(sample);
  }
  float t = (millis()/1000.0f) - demoStartTime + startTime;
  //println(t);
  background(0);
  lights();  
  
  translate(width/2, height/2, 0);
  stroke(255);
  
  // smooth the rms data by smoothing factor
  sum += (rms.analyze() - sum) * smooth_factor;
  
    float stage1 = 40;
    float stage2 = 48;
    float stage3 = 50;
    float stage4 = 65;
    
    if(t<stage1){
      rotateX(-PI/6);
      rotateY(spinSpeed*t);
      scale(sum*5);
      drawSpinner();
      //drawTriangle();
    }else if(t<stage2){
      rotateX(-PI/6);
      rotateY(spinSpeed*t);
      scale(sum*5);
      pushMatrix();
      float disp = (t-stage1)*100;
      translate(disp, 0, 0);
      drawSpinner();
      popMatrix();
      pushMatrix();
      translate(-disp, 0, 0);
      drawSpinner();
      popMatrix();
    }else if(t<stage3){
      rotateX(PI/2);
      rotateY(spinSpeed*t);
      scale(0.25f);
      drawSpinner();
    }else if(t<stage4){
      rotateX(PI/2);
      rotateY(spinSpeed*t);
      scale(0.25f);
      float disp = (t-stage3)*100;
      int counter = 0;
      for(float i = 0; i < 2*PI; i+=(PI/3)){
        pushMatrix();
        translate(disp*sin(i),0,disp*cos(i));
        if(counter%2 == 0) rotateY(spinSpeed*t);
        drawSpinner();
        popMatrix();
        counter++;
      }
    }else{
      done = true;
      textSize(32);
      fill(0, 102, 153);
      float t_diff = (t-stage4);
      text("Speed: " + (spinSpeed+t_diff) , -width*0.5f+50, -height*0.5f+50);
      rotateX(PI/2);
      rotateY((spinSpeed+t_diff)*t);
      scale(10);
      scale(sum);
      drawSpinner();
    }
  }
  
  
  public boolean isOver() {
    if(done){
      sample.stop();
    }
    return done;
  }
  
  public void drawSpinner(){
    pushMatrix();
    scale(100);
    shape(spinner, 0, 0);
    popMatrix();
  }
  
}



class TravercingBall extends demoBase {
  Minim minim;
  AudioPlayer s;
  ddf.minim.analysis.FFT f;

  float specLow = 0.03f;   // 3%
  float specMid = 0.125f;  // 12.5%
  float specHi = 0.20f;    // 20%

  
  int scoreLow = 0;
  int scoreMid = 0;
  int scoreHi = 0;

  ArrayList<Anomaly> a;
  Road road;

  boolean done;
  float startTime;
  float angle;
  
  public TravercingBall(AudioPlayer s, ddf.minim.analysis.FFT f) {
    this.s = s;
    this.f = f;
    startTime = - 1;
    
  
    road = new Road();
    a = new ArrayList<Anomaly>();
    a.add(new Anomaly((width / 2)));
  }
  
  
  public void playDemo() {
    if (startTime == -1) {
      angle = 0;
      done = false;
      s.play(44 * 1000 + 500);
      startTime =  millis()/1000.0f;
    }
    float t = millis()/1000.0f - startTime;
    if(t > 20) {
       if(angle < 90) {
         angle += 8;
       }
       else {
        s.pause();
        done = true;
       }
    }
    drawBackGround();
    calculateScores();
    doStuff();
    
  }
  
  public void doStuff() {
    for (int i = a.size()-1; i >= 0; i--) {
    Anomaly h = a.get(i);
    if(scoreLow > 400){
      h.bounce();
    }
    
    h.draw(100+scoreLow, 100+scoreMid, 100+scoreHi, scoreLow / 4);
  } 

  road.update(100+scoreLow, 100+scoreMid, 100+scoreHi);
  }
  public void calculateScores() {
    scoreLow = 0;
    scoreMid = 0;
    scoreHi = 0;
    f.forward(s.mix); // s = AudioPlayer
    for(int i = 0; i < f.specSize()*specLow; i++)
    {
      scoreLow += f.getBand(i);
    }
    
    for(int i = (int)(f.specSize()*specLow); i < f.specSize()*specMid; i++)
    {
      scoreMid += f.getBand(i);
    }
    
    for(int i = (int)(f.specSize()*specMid); i < f.specSize()*specHi; i++)
    {
      scoreHi += f.getBand(i);
    }
      
  }
  public void drawBackGround() {
    rotateY(radians(angle));
    background( scoreLow / 100, scoreHi / 10, scoreMid / 10);
  }
  
  public boolean isOver() {
    if(done) {
      s.close();
      startTime = -1;
    }
    return done;
  }
}

class lineDemo extends demoBase {
  PShape boxi;
PShape globe;
float zDimension = 0;
Boolean forward;

ArrayList<Integer> listOfLines;
int colorChange = 0;
int RGBSwitch = 0;
int x = -990;
int y = 0;
int x2 = 990;
int y2 = 0;
int x3 = 150;
int x4 = -150;
  
  float startTime;
  SoundFile sample;
  
  public lineDemo(SoundFile sample) {
    zDimension=1000;
  forward = true;
    startTime = -1;
    this.sample = sample;
  }
  public void playDemo() {
    if(startTime == -1) {
      startTime = millis()/1000.0f;
      sample.loop();
    }
 background(255);
  noFill();
  
  println(colorChange + " " + forward + " " + RGBSwitch);
  if (colorChange < 255 && forward && RGBSwitch == 0) {
    stroke(min(colorChange,255),0,0);
    colorChange = colorChange + 5;
    if (colorChange == 255 || colorChange == 250)
      RGBSwitch = 1;
  } else if (colorChange <= 255 && forward && RGBSwitch == 1) {
    stroke(0,min(colorChange,255),0);
    colorChange = colorChange - 5;
    if (colorChange == 0)
      RGBSwitch = 2;
  } else if (colorChange < 255 && forward && RGBSwitch == 2) {
    stroke(0,0,min(colorChange,255));
    colorChange = colorChange + 5;
    if (colorChange == 250 || colorChange == 250)
      RGBSwitch = 0;
  } else {
    
  }
  
  translate(width/2, height/2);
  for (int i = 0; i < 35; i++) {  
    fill(i * 150/35);
    line(x+i,y+100*i,x2-i,y2-100*i); 
    line(x+i,y-100*i,x2-i,y2+100*i); 
  }
  
  stroke(255,0,0);
  strokeWeight(5);
  float seconds = millis() / 1000.0f - startTime;

  if(forward){
    x3+= 1 + 5.9f*seconds;
    x4-= 1 + 5.9f*seconds;

    line(x3,2000,x3,-2000);
    line(x4,2000,x4,-2000);
    
  }
  else{
    x3-= 1 + 5.9f*seconds;
    x4+= 1 + 5.9f*seconds;
    line(x3,2000,x3,-2000);
    line(x4,2000,x4,-2000);

  }
  
  if (x3 > 800 && forward) {
    forward = false; 
  } 
  else {
    if(x3 < 0 && !forward){
      x3-= 1 + 1.9f*seconds;
      x4+= 1 + 1.9f*seconds;
      line(x3,2000,x3,-2000);
      line(x4,2000,x4,-2000);
      forward = true;
    }
  }
  }
  
  public boolean isOver(){
    boolean result = false;
    if(millis()/1000.0f - startTime > 7) {
      sample.stop();
      result = true;
    }
    return result;
  }
  
}
  public void settings() {  fullScreen(P3D); }
  static public void main(String[] passedArgs) {
    String[] appletArgs = new String[] { "--present", "--window-color=#666666", "--hide-stop", "AwsumDemo" };
    if (passedArgs != null) {
      PApplet.main(concat(appletArgs, passedArgs));
    } else {
      PApplet.main(appletArgs);
    }
  }
}
