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

import processing.sound.*; 

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



PGraphics buffer0;
PGraphics buffer1;
PGraphics buffer2;
PGraphics buffer3;
PImage flameBackground;
PGraphics canvas;

int w = 896;
int h = 504;

SoundFile music;
float ystart = 0.0f;
Metaball[] blobs = new Metaball[8];
static int radius = 20;
int startTime;
ArrayList<Integer> timings;
ArrayList<Integer> timings2;

public void setup() {
  init();
  
  //size(1920,1080, P3D);
  

  buffer0 = createGraphics(w, h);
  buffer1 = createGraphics(w, h);
  buffer2 = createGraphics(w, h);
  buffer3 = createGraphics(w, h);
  buffer3.noSmooth();

  canvas = createGraphics(w, h, P3D);

  flameBackground = createImage(w, h, RGB);
  colorMode(RGB);

  for (int i = 0; i < blobs.length; i++) {
    blobs[i] = new Metaball(random(w), random(h));
  }
  
  canvas.beginDraw();
  canvas.noSmooth();
  canvas.background(0);
  canvas.endDraw();
  
  music = new SoundFile(this, "beat.mp3");
  thread("playMusic");
  if (startTime == 0){
    delay(1);
  }
}

public void playMusic(){
  music.play();
  startTime = millis();
}

/*
void keyPressed() {
  println("timings2.add(" + (millis()-startTime) + ");");
}*/

public void cool() {
  flameBackground.loadPixels();
  float xoff = 0.0f; 
  float increment = 0.2f;
  
  for (int x = 0; x < w; x++) {
    xoff += increment;   
    float yoff = ystart;  
    for (int y = 0; y < h; y++) {
      yoff += increment; 

      float n = noise(xoff, yoff);     
      float bright = pow(n, 2) * 255;
      flameBackground.pixels[x+y*w] = color(bright);
    }
  }

  flameBackground.updatePixels();
}


public void metaballsFire(boolean centersOnly) {
  if (centersOnly){
    buffer1.beginDraw();
    buffer1.loadPixels();
  } else {
    buffer0.beginDraw();
    buffer0.loadPixels();
  }
  for (int x = 0; x < w; x++) {
    for (int y = 0; y < h; y++) {
      int index = x + y * w;
      float sum = 0;
      for (Metaball b : blobs) {
        float d = dist(x, y, b.pos.x, b.pos.y);
        sum += 128 * b.r / (d*d/2);
      }
      
      if (centersOnly && sum > 245){
        buffer1.pixels[index] = color(sum, sum / 2, sum/4);
      } else if(!centersOnly) {
        buffer0.pixels[index] = color(sum * 2, sum, sum/2);
      }
    }
  }
  if (centersOnly){
    buffer1.updatePixels();
    buffer1.endDraw();
  } else {
    buffer0.updatePixels();
    buffer0.endDraw();
  }

}

public void fireballs(){
  metaballsFire(true);
  metaballsFire(false);

  cool();
  canvas.beginDraw();
  canvas.background(0);
  buffer2.beginDraw();
  buffer1.loadPixels();
  buffer2.loadPixels();

  float newC = 0;
  int c1;
  int c2;
  int c3;
  int c4;
  int c5;

  for (int x = 1; x < w-1; x++) {
    for (int y = 1; y < h-1; y++) {
      c1 = buffer1.pixels[(x+1) + (y) * w];
      c2 = buffer1.pixels[(x-1) + (y) * w];
      c3 = buffer1.pixels[(x) + (y+1) * w];
      c4 = buffer1.pixels[(x) + (y-1) * w];

      c5 = flameBackground.pixels[(x) + (y) * w];
      newC = brightness(c1) + brightness(c2)+ brightness(c3) + brightness(c4);
      newC = newC * 0.25f - brightness(c5);

      buffer2.pixels[(x) + (y-1) * w] = color(newC, newC, newC);
      
      if (x < 4 || x > w - 4 || y < 4 || y > h - 4){
        buffer1.pixels[(x) + (y) * w] = color(0);
        buffer2.pixels[(x) + (y) * w] = color(0);
      }
    }
  }
  buffer2.updatePixels();
  buffer2.endDraw();

  // Swap
  PGraphics temp = buffer1;
  buffer1 = buffer2;
  buffer2 = temp;
  canvas.blendMode(SCREEN);
  canvas.image(buffer0, -1, -1, w + 2, h + 2);
  canvas.image(buffer1, -1, -1, w + 2, h + 2);
  canvas.blendMode(BLEND);
  canvas.endDraw();
}


public void metaballsElectric(float r, float g, float b, boolean centersOnly) {
  if (centersOnly){
    buffer1.beginDraw();
    buffer1.loadPixels();
    buffer1.background(0);
  } else {
    buffer0.beginDraw();
    buffer0.loadPixels();
  }

  for (int x = 0; x < w; x++) {
    for (int y = 0; y < h; y++) {
      int index = x + y * w;
      float sum = 0;
      for (Metaball blob : blobs) {
        float d = dist(x, y, blob.pos.x, blob.pos.y);
        sum += 128 *  blob.r / (d*d/2);
      }
      
      if (centersOnly && sum > 240){
        buffer1.pixels[index] = color(sum * 4.55f * r, sum * 2.55f / 2 * g, sum * 2.55f/4* b);
      } else if(!centersOnly) {
        buffer0.pixels[index] = color(sum * 2.55f * r, sum * 2.55f * g, sum * 2.55f * b);
      } else {
        buffer1.pixels[index] = color(0, 0, 0, 0);
      }

    }
  }

  if (centersOnly){
    buffer1.updatePixels();
    buffer1.endDraw();
  } else {
    buffer0.updatePixels();
    buffer0.endDraw();
  } 
}


//ELECTRICITY
public void electricballs(){
  buffer3.beginDraw();
  metaballsElectric(0.1f, 0.1f, 0.1f, true);
  colorMode(RGB);
  if (millis() - startTime > 38000 && millis() - startTime < 42000){
    metaballsElectric(0.6f, 0.4f, 0.7f, false);
  } else {
    metaballsElectric(0, 0.6f, 0.7f, false);
  }
  buffer3.background(0);
  for (Metaball b : blobs) {
      for (Metaball b2 : blobs) {
        if (b != b2){
          ArrayList<PVector> path = new ArrayList<PVector>();

          float dist = b.pos.dist(b2.pos);
          for (int i = 0; i < (int) (3 - dist / 120); i++){
            for (PVector v : drawJumpingLine(getRandomPointsInsideBall(b), getRandomPointsInsideBall(b2))){
              path.add(v);
            }
          }
          
          // 3 Loops to get them into right order
          for (int i = 1; i < path.size(); i++){
            PVector point1 = path.get(i-1);
            PVector point2 = path.get(i);
            if (Math.max(400 - dist, 0) > 0){
              buffer3.strokeWeight(12);
              buffer3.stroke(0,150,200, map(Math.max(400 - dist, 0), 0, 400,0,50));
              buffer3.line(point1.x, point1.y, point2.x, point2.y);
            }
          }
          for (int i = 1; i < path.size(); i++){
            PVector point1 = path.get(i-1);
            PVector point2 = path.get(i);
            if (Math.max(350 - dist, 0) > 0){
              buffer3.strokeWeight(6);
              buffer3.stroke(50,200,200, map(Math.max(350 - dist, 0), 0, 400,0,150));
              buffer3.line(point1.x, point1.y, point2.x, point2.y);
            }
          }
          for (int i = 1; i < path.size(); i++){
              PVector point1 = path.get(i-1);
              PVector point2 = path.get(i);
              if (Math.max(350 - dist, 0) > 0){
                buffer3.strokeWeight(2);
                buffer3.stroke(50,200,200, map(Math.max(350 - dist, 0), 0, 400,0,150));
                buffer3.line(point1.x, point1.y, point2.x, point2.y);
              }
           }
        }
      }
  }
  
  // Swap buffers
  buffer3.blendMode(ADD);
  buffer3.image(buffer0, 0, 0);
  buffer3.image(buffer1, 0, 0);
  buffer3.blendMode(BLEND);
  buffer3.endDraw();
  canvas.beginDraw();
  canvas.image(buffer3, 0, 0);
  canvas.endDraw();

}

// Random stuff to sync effects
int timer = 0;
boolean mode = true;
int lastMillis = 0;

public void draw() {
  update();
  blendMode(REPLACE);
  image(canvas, 0, 0, width, height); 
  lastMillis = millis();
  if (lastMillis > 66500){
    exit();
  }
}

public void update(){
   
  if (timings.size() > 0 && (timings.get(0)/1000*1000 + (timings.get(0)/100)%10*100) <= millis() - startTime){
    timings.remove(0);
    int i = 0;  
    for (Metaball b : blobs) {
      i++;
      if (!mode && i > blobs.length / 2){
        break;
      }
      timer++;
      if (timer % 3 == 0){
           b.randomize();
      }
      
    }
  }
  
  if (timings2.size() > 0 && timings2.get(0) <= millis() - startTime + 200){
    timings2.remove(0);
    mode = !mode;
    

    for (Metaball b : blobs) {
      timer++;
      if (timer % 2 == 0){
        b.randomize();
      }
    }
  }
  
  if (millis() - startTime > 45000){
   for (Metaball b : blobs) {
     b.r -= (millis() - lastMillis)*40;
   }
  }  
  if (millis() - startTime < 7000){
   for (Metaball b : blobs) {
     b.r += random(2, 10);
   }
  }


  if (!mode){
    fireballs();
  } else {
    electricballs();
  } 
  for (Metaball b : blobs) {
    b.update();
  }
}

public float deltaTime(){
  return ((float) millis() - lastMillis) / 100;
}

class Metaball {
  PVector pos;
  float r;
  PVector vel;

  Metaball(float x, float y) {
    pos = new PVector(x, y);
    vel = PVector.random2D();
    vel.mult(random(1, 3));
    r = random(0, 0);
  }
  
  public void randomize(){
    float size;
    if (millis() <= 30000){
      size = map(millis(),0,30000, 500, 1500); 
    } else { 
      size = map(millis(),30000,60000, 1500, 500); 
    }
    if (mode){
      size /= 4;
    }
    r = random(size, size+3000);
    vel = PVector.random2D();
    float speed;
    if (millis() <= 30000){
      speed = map(millis(),0,30000, 3, 20); 
    } else { 
      speed = map(millis(),30000,60000, 20, 10); 
    }
    
    vel.mult(random(speed, speed * 1.3f));

  }
  

  public void update() {
    pos.add(vel.copy().mult(deltaTime())); 
    if (pos.x > w || pos.x < 0) {
      vel.x *= -1;
      if (pos.x > w){
        pos.x = w;
      } else {
        pos.x = 0;
      }
    }
    if (pos.y > h|| pos.y < 0) {
      vel.y *= -1;
      if (pos.y > h){
        pos.y = h;
      } else {
        pos.y = 0;
      }
    }
  }

  public void show() {
    canvas.noFill();
    canvas.stroke(0);
    canvas.strokeWeight(4);
    canvas.ellipse(pos.x, pos.y, r*2, r*2);
  }
}

public ArrayList<PVector> drawJumpingLine(PVector original1, PVector original2){
  ArrayList<PVector> newPath = new ArrayList<PVector>();
  
  PVector midPoint = original1.copy().add(original2).mult(0.5f);
  PVector midPoint2 = original1.copy().add(midPoint).mult(0.5f);
  PVector midPoint3 = midPoint.copy().add(original2).mult(0.5f);

  float distance =  original1.dist(original2);
  
  float standardDeviation = 0.06f * distance;
  PVector v = new PVector((randomGaussian() * standardDeviation) + midPoint.x,(randomGaussian() * standardDeviation) + midPoint.y);
  PVector v3 = new PVector((randomGaussian() * standardDeviation) + midPoint2.x,(randomGaussian() * standardDeviation) + midPoint2.y);
  PVector v4 = new PVector((randomGaussian() * standardDeviation) + midPoint3.x,(randomGaussian() * standardDeviation) + midPoint3.y);

  newPath.add(original1);
  newPath.add(v3);
  newPath.add(v);
  newPath.add(v4);
  newPath.add(original2);

  return newPath;  
}

  

public PVector getRandomPointsInsideBall(Metaball b) {
  float r = b.r/500;
  float theta1 = randomGaussian() * PI/4;

  PVector v1 = new PVector(b.pos.x + r*cos(theta1),b.pos.y + r*sin(theta1));

  return v1;
}


// Init timings :- D
public void init(){
timings = new ArrayList<Integer>();
timings2 = new ArrayList<Integer>();

timings.add(4582);
timings.add(5332);
timings.add(6061);
timings.add(6819);
timings.add(7497);
timings.add(8250);
timings.add(8966);
timings.add(9689);
timings.add(10412);
timings.add(11177);
timings.add(11863);
timings.add(12621);
timings.add(13304);
timings.add(14066);
timings.add(14778);
timings.add(15534);
timings.add(16210);
timings.add(16963);
timings.add(17639);
timings.add(18431);
timings.add(19108);
timings.add(19859);
timings.add(20612);
timings.add(21287);
timings.add(22043);
timings.add(22838);
timings.add(23519);
timings.add(24238);
timings.add(24951);
timings.add(25702);
timings.add(26382);
timings.add(27138);
timings.add(27855);
timings.add(28571);
timings.add(29324);
timings.add(30073);
timings.add(30748);
timings.add(31542);
timings.add(32223);
timings.add(32937);
timings.add(33725);
timings.add(34481);
timings.add(35199);
timings.add(35875);
timings.add(36561);
timings.add(37383);
timings.add(38056);
timings.add(38775);
timings.add(39494);
timings.add(40242);
timings.add(40953);
timings.add(41669);
timings.add(42383);
timings.add(43143);
timings.add(43899);
timings.add(44577);
timings.add(45331);
timings.add(46091);
timings.add(46853);
timings.add(47526);
timings.add(48270);
timings.add(49052);
timings.add(49823);
timings.add(50595);
timings.add(51367);
timings.add(52176);
timings.add(52943);
timings.add(53744);
timings.add(54543);
timings.add(55382);
timings.add(56157);
timings.add(57007);
timings.add(57848);
timings.add(58689);
timings.add(59529);


timings2.add(7450);
timings2.add(13331);

timings2.add(17770); //FAST PART
timings2.add(17917);
timings2.add(18103);
timings2.add(18250);
timings2.add(18476);
timings2.add(18625);
timings2.add(18810);
timings2.add(18969);
timings2.add(19158); 
timings2.add(19359);
timings2.add(19520);
timings2.add(19729);
timings2.add(19914);
timings2.add(20100);
timings2.add(20249);
timings2.add(20452);
timings2.add(20634); //END OF FAST



timings2.add(23552); // FAST PART
timings2.add(23740);
timings2.add(23890);
timings2.add(24076);
timings2.add(24283);
timings2.add(24439);
timings2.add(24628);
timings2.add(24795);
timings2.add(24981);
timings2.add(25170);
timings2.add(25356);
timings2.add(25543);
timings2.add(25702);
timings2.add(25877);
timings2.add(26057);
timings2.add(26241);
timings2.add(26441); // END OF FAST

timings2.add(29367); // FAST
timings2.add(29521);
timings2.add(29703);
timings2.add(29896);
timings2.add(30080);
timings2.add(30273);
timings2.add(30463);
timings2.add(30599);
timings2.add(30786);
timings2.add(30976);
timings2.add(31159);
timings2.add(31351);
timings2.add(31502);
timings2.add(31691);
timings2.add(31877);
timings2.add(32075); 
timings2.add(32275); // END OF FAST

timings2.add(35137);
timings2.add(35361);
timings2.add(35510);
timings2.add(35706);
timings2.add(35915);
timings2.add(36097);
timings2.add(36281);
timings2.add(36458);
timings2.add(36645);
timings2.add(36830);
timings2.add(37017);
timings2.add(37173);
timings2.add(37335);
timings2.add(37503);
timings2.add(37670);
timings2.add(37862);
timings2.add(38062);// END OF FAST

timings2.add(40958);
timings2.add(41162);
timings2.add(41327);
timings2.add(41507);
timings2.add(41694);
timings2.add(41882);
timings2.add(42067);
timings2.add(42239);
timings2.add(42442);
timings2.add(42622);
timings2.add(42804);
timings2.add(42966);
timings2.add(43151);
timings2.add(43340);
timings2.add(43526);
timings2.add(43684); 
timings2.add(43884);// END OF FAST
timings2.add(50000);
timings2.add(53000);


}

  public void settings() {  fullScreen(P3D);  noSmooth(); }
  static public void main(String[] passedArgs) {
    String[] appletArgs = new String[] { "sketch_190731a" };
    if (passedArgs != null) {
      PApplet.main(concat(appletArgs, passedArgs));
    } else {
      PApplet.main(appletArgs);
    }
  }
}
