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

import controlP5.*; 
import ddf.minim.*; 
import java.awt.Polygon; 
import ddf.minim.*; 
import com.dhchoi.CountdownTimer; 
import com.dhchoi.CountdownTimerService; 
import ddf.minim.*; 
import ddf.minim.ugens.*; 
import ddf.minim.analysis.*; 
import controlP5.*; 

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




//-----------------------CurrentState-----------------------

State currentState;
//-----------------------CP5--------------------------------
ControlP5 cp5;
Button b;
int nButtons = 1;
String LoadMessages;
PFont font;
boolean[] firstInicialization;
PApplet app;
PImage sprite;
PlayState playState;

Minim minim;
AudioInput in;
public void setup()
{
   //----------------------------------ControlP5---------------------------------
  cp5 = new ControlP5(this);
  reserveSpaceForButtons();
  // -------------------------------Screen*----------------------------
  
  
  surface.setResizable(true);
  background(0, 0, 0);
  noStroke();
  app = this;
  //----------------------------------Font-------------------------
  font = loadFont("SpaceFrigate-48.vlw");
    //--------------------------------Mic-------------------------------------
  minim = new Minim(this);
    in = minim.getLineIn();
  //-----------------------------------State--------------------
  currentState = new MainMenuState();
  currentState.setup();
 

}

public void draw()
{
  currentState.draw();
}

public void reserveSpaceForButtons()
{
  cp5 = new ControlP5(this);
  //b = new Button[nButtons];
  firstInicialization = new boolean[nButtons];  
  
  ControlFont buttonFont = new ControlFont(createFont("spacefr.ttf", 75));
  
  
  //----------------------------------ControlP5-----------------------------------
  firstInicialization[0] = true;
  b= cp5.addButton("Jugar", 0)
    .setColorActive(color(136,80,160)) // color for click
    .setColorBackground(color(160, 136, 216)) // default color
    .setColorForeground(color(136,80,160)) // color for mouse-over
    .setFont(buttonFont)
    .setValue(0)
    .setPosition(width/2-60*3, height*3/4)
    .setSize(width*3/9, height*1/9)
    ;
    
}

public void controlEvent(ControlEvent theEvent) 
{
  println(theEvent.getController().getName());
}
public void Jugar(int theValue) 
{
  if (!firstInicialization[theValue])
  {
    b.setVisible(false);
    //cp5.remove("Jugar");
    //currentState = new PlayState();
    playState = new PlayState();
    currentState = playState;
    
    currentState.setup();
  } else
  {
    firstInicialization[theValue] = false;
  }
}
public void onTickEvent(CountdownTimer t, long timeLeftUntilFinish) 
{
  playState.onTickEvent(t, timeLeftUntilFinish);
}

public void onFinishEvent(CountdownTimer t) 
{
  playState.onFinishEvent(t);
}


class Circle{
  
  float x;
  float y;
  float r;
  int fColor; // fill color
  int lColor;  // line color
  boolean hasStroke;
  float lWeight; // line Weight
  
  Circle(float x, float y, float r){
    this.x = x;
    this.y = y;
    this.r = r;
    fColor = color(255);
    lColor = color(255);
    hasStroke = false;
    lWeight = 1;
  }
  
  public void draw(){
    fill(fColor);
    if(hasStroke){
      stroke(lColor,70);
      strokeWeight(lWeight);
    }else{
      noStroke();
    }
    ellipse(x,y,r*2,r*2);
    strokeWeight(1);
  }
  
  public void setPosition(float x, float y){
    this.x = x;
    this.y = y;
  }
  
  public void setRadius(float r){
    this.r = r;
  }
  
  public void setFillColor(float r, float g, float b){
    fColor = color(r,g,b);
  }
  
  public void setLineColor(float r, float g, float b){
    lColor = color(r,g,b);
  }
  
}



class Gesture {

  float  damp = 5.0f;
  float  dampInv = 1.0f / damp;
  float  damp1 = damp - 1;

  int w;
  int h;
  int capacity;
  
  int waveDirection;
 
 float jumpInScreen;

  Vec3f path[];
  int crosses[];
  Polygon polygons[];
  int nPoints;
  int nPolys;

  float jumpDx, jumpDy;
  boolean exists;
  float INIT_TH = 1.5f;
  float thickness = INIT_TH;

  Gesture(int mw, int mh) {
    w = mw;
    h = mh;
    capacity = 750;
    path = new Vec3f[capacity];
    polygons = new Polygon[capacity];
    crosses  = new int[capacity];
    for (int i=0;i<capacity;i++) {
      polygons[i] = new Polygon();
      polygons[i].npoints = 4;
      path[i] = new Vec3f();
      crosses[i] = 0;
    }
    nPoints = 0;
    nPolys = 0;

    exists = false;
    jumpDx = 0;
    jumpDy = 0;
  }

  public void clear() {
    nPoints = 0;
    exists = false;
    thickness = INIT_TH;
  }

  public void clearPolys() {
    nPolys = 0;
  }

  public void addPoint(float x, float y) {

    if (nPoints >= capacity) {
      // there are all sorts of possible solutions here,
      // but for abject simplicity, I don't do anything.
    } 
    else {
      float v = distToLast(x, y);
      float p = getPressureFromVelocity(v);
      path[nPoints++].set(x,y,p);

      if (nPoints > 1) {
        exists = true;
        jumpDx = path[nPoints-1].x - path[0].x;
        jumpDy = path[nPoints-1].y - path[0].y;
      }
    }

  }

  public float getPressureFromVelocity(float v) {
    final float scale = 18;
    final float minP = 0.02f;
    final float oldP = (nPoints > 0) ? path[nPoints-1].p : 0;
    return ((minP + max(0, 1.0f - v/scale)) + (damp1*oldP))*dampInv;
  }

  public void setPressures() {
    // pressures vary from 0...1
    float pressure;
    Vec3f tmp;
    float t = 0;
    float u = 1.0f / (nPoints - 1)*TWO_PI;
    for (int i = 0; i < nPoints; i++) {
      pressure = sqrt((1.0f - cos(t))*0.5f);
      path[i].p = pressure;
      t += u;
    }
  }

  public float distToLast(float ix, float iy) {
    if (nPoints > 0) {
      Vec3f v = path[nPoints-1];
      float dx = v.x - ix;
      float dy = v.y - iy;
      return mag(dx, dy);
    } 
    else {
      return 30;
    }
  }

  public void compile() {
    // compute the polygons from the path of Vec3f's
    if (exists) {
      clearPolys();

      Vec3f p0, p1, p2;
      float radius0, radius1;
      float ax, bx, cx, dx;
      float ay, by, cy, dy;
      int   axi, bxi, cxi, dxi, axip, axid;
      int   ayi, byi, cyi, dyi, ayip, ayid;
      float p1x, p1y;
      float dx01, dy01, hp01, si01, co01;
      float dx02, dy02, hp02, si02, co02;
      float dx13, dy13, hp13, si13, co13;
      float taper = 1.0f;

      int  nPathPoints = nPoints - 1;
      int  lastPolyIndex = nPathPoints - 1;
      float npm1finv =  1.0f / max(1, nPathPoints - 1);

      // handle the first point
      p0 = path[0];
      p1 = path[1];
      radius0 = p0.p * thickness;
      dx01 = p1.x - p0.x;
      dy01 = p1.y - p0.y;
      hp01 = sqrt(dx01*dx01 + dy01*dy01);
      if (hp01 == 0) {
        hp02 = 0.0001f;
      }
      co01 = radius0 * dx01 / hp01;
      si01 = radius0 * dy01 / hp01;
      ax = p0.x - si01; 
      ay = p0.y + co01;
      bx = p0.x + si01; 
      by = p0.y - co01;

      int xpts[];
      int ypts[];

      int LC = 20;
      int RC = w-LC;
      int TC = 20;
      int BC = h-TC;
      float mint = 0.618f;
      float tapow = 0.4f;

      // handle the middle points
      int i = 1;
      Polygon apoly;
      for (i = 1; i < nPathPoints; i++) {
        taper = pow((lastPolyIndex-i)*npm1finv,tapow);

        p0 = path[i-1];
        p1 = path[i  ];
        p2 = path[i+1];
        p1x = p1.x;
        p1y = p1.y;
        radius1 = Math.max(mint,taper*p1.p*thickness);

        // assumes all segments are roughly the same length...
        dx02 = p2.x - p0.x;
        dy02 = p2.y - p0.y;
        hp02 = (float) Math.sqrt(dx02*dx02 + dy02*dy02);
        if (hp02 != 0) {
          hp02 = radius1/hp02;
        }
        co02 = dx02 * hp02;
        si02 = dy02 * hp02;

        // translate the integer coordinates to the viewing rectangle
        axi = axip = (int)ax;
        ayi = ayip = (int)ay;
        axi=(axi<0)?(w-((-axi)%w)):axi%w;
        axid = axi-axip;
        ayi=(ayi<0)?(h-((-ayi)%h)):ayi%h;
        ayid = ayi-ayip;

        // set the vertices of the polygon
        apoly = polygons[nPolys++];
        xpts = apoly.xpoints;
        ypts = apoly.ypoints;
        xpts[0] = axi = axid + axip;
        xpts[1] = bxi = axid + (int) bx;
        xpts[2] = cxi = axid + (int)(cx = p1x + si02);
        xpts[3] = dxi = axid + (int)(dx = p1x - si02);
        ypts[0] = ayi = ayid + ayip;
        ypts[1] = byi = ayid + (int) by;
        ypts[2] = cyi = ayid + (int)(cy = p1y - co02);
        ypts[3] = dyi = ayid + (int)(dy = p1y + co02);

        // keep a record of where we cross the edge of the screen
        crosses[i] = 0;
        if ((axi<=LC)||(bxi<=LC)||(cxi<=LC)||(dxi<=LC)) { 
          crosses[i]|=1; 
        }
        if ((axi>=RC)||(bxi>=RC)||(cxi>=RC)||(dxi>=RC)) { 
          crosses[i]|=2; 
        }
        if ((ayi<=TC)||(byi<=TC)||(cyi<=TC)||(dyi<=TC)) { 
          crosses[i]|=4; 
        }
        if ((ayi>=BC)||(byi>=BC)||(cyi>=BC)||(dyi>=BC)) { 
          crosses[i]|=8; 
        }

        //swap data for next time
        ax = dx; 
        ay = dy;
        bx = cx; 
        by = cy;
      }

      // handle the last point
      p2 = path[nPathPoints];
      apoly = polygons[nPolys++];
      xpts = apoly.xpoints;
      ypts = apoly.ypoints;

      xpts[0] = (int)ax;
      xpts[1] = (int)bx;
      xpts[2] = (int)(p2.x);
      xpts[3] = (int)(p2.x);

      ypts[0] = (int)ay;
      ypts[1] = (int)by;
      ypts[2] = (int)(p2.y);
      ypts[3] = (int)(p2.y);

    }
  }

  public void smooth() {
    // average neighboring points

    final float weight = 18;
    final float scale  = 1.0f / (weight + 2);
    int nPointsMinusTwo = nPoints - 2;
    Vec3f lower, upper, center;

    for (int i = 1; i < nPointsMinusTwo; i++) {
      lower = path[i-1];
      center = path[i];
      upper = path[i+1];

      center.x = (lower.x + weight*center.x + upper.x)*scale;
      center.y = (lower.y + weight*center.y + upper.y)*scale;
    }
  }
}

class InstructionState extends State
{
  PImage[] Instructions;
  public void setup()
  {
  }
  public void draw()
  { 
    background(0);
    //Agregar Im\u00e1genes
  }

}

class MainMenuState extends State
{
  //Minim minim;
  //AudioInput in;
  boolean createdWave = false;
  int currentGestureID;
  int nGestures = 1;
  Gesture[] gestureArray;
  PShader nebula;

  PImage bg_img;
  PFont font;
  int direction = 0;
  float posY;
  public void setup()
  {
    b.setVisible(true);
    //----------------------------------Microphone------------------
    //minim = new Minim(this);
    //in = minim.getLineIn();
    //--------------------------------Shader------------------------------
    currentGestureID = -1;
    bg_img = loadImage("hubble.jpg");
    bg_img.resize(width, height);
    nebula = loadShader("nebula.glsl");
    nebula.set("resolution", PApplet.parseFloat(width), PApplet.parseFloat(height));
    font = loadFont("SpaceFrigate-48.vlw");
    //---------------------------------------WaveVars------------------------------
    direction = 0;
    createdWave = false;
    posY = height*1/4+ 10 + 15.0f*((float)(nGestures)/10);
    currentGestureID = -1;
    gestureArray = new Gesture[nGestures];
    for (int i = 0; i < nGestures; i++) {
      gestureArray[i] = new Gesture(width, height);
    }
    clearGestures();
  }
  public void draw()
  {

    background(0);
    nebula.set("time", millis() / 500.0f);  
    shader(nebula); 
    rect(0, 0, width, height);
    
    resetShader();
    
    for (int i = 0; i < in.bufferSize()*3/4 - 1; i++)
    {
      if ((in.left.get(i)*50 >1.2f || in.left.get(i)*50 < -1.2f))
      {
        createdWave = true;      
        currentGestureID = (currentGestureID+1) % nGestures;
        createGeometry();
      }
      if (createdWave)
      {
        break;
      }
    }
    
    updateGeometry();
    fill(255, 255, 245);
    for (int i = 0; i < nGestures; i++) 
    {
      renderGesture(gestureArray[i], width, height);
    }
    //------------------------------------------------------Text and GUI------------------------------------------
    textFont(font, 90);
    fill(255);
    textAlign(CENTER);
    text("PRANAWAVE", width/2, height*1/4);
  }
  //-------------------------------------------------WAVES--------------------------------------------
  public void createGeometry()
  {
    Gesture G = new Gesture(width, height);
    G.clear();
    G.clearPolys();
    G.jumpInScreen = posY*currentGestureID;
    direction = 1 - direction;

    G.waveDirection = direction;
    println(direction);
    for (int i = 0; i < in.bufferSize() - 1; i++)
    {
      //G.addPoint(i + (width*2/8) * 250, G.jumpInScreen + in.left.get(i)*450);
      G.addPoint(i +width/2 * 350, height/2 + in.left.get(i)*650);
    }
    if (!existsAlready(G))
    {
      gestureArray[currentGestureID] = G;
    }
  }

  public boolean existsAlready(Gesture newElement )
  {

    for (int i = 0; i < gestureArray.length-1; i++)
    {
      for (int j = 0; j < gestureArray[i].path.length -1; j++)
      {

        if (newElement.path[j].y - newElement.jumpInScreen == gestureArray[i].path[j].y - gestureArray[i].jumpInScreen)
        {
          return true;
        }
      }
    }
    return false;
  }

  public void renderGesture(Gesture gesture, int w, int h) {
    if (gesture.exists) {
      if (gesture.nPolys > 0) {
        Polygon polygons[] = gesture.polygons;
        int crosses[] = gesture.crosses;

        int xpts[];
        int ypts[];
        Polygon p;
        int cr;

        beginShape(QUADS);
        int gnp = gesture.nPolys;
        for (int i=0; i<gnp; i++) {

          p = polygons[i];
          xpts = p.xpoints;
          ypts = p.ypoints;

          vertex(xpts[0], ypts[0]);
          vertex(xpts[1], ypts[1]);
          vertex(xpts[2], ypts[2]);
          vertex(xpts[3], ypts[3]);

          if ((cr = crosses[i]) > 0) {
            if ((cr & 3)>0) {
              vertex(xpts[0]+w, ypts[0]);
              vertex(xpts[1]+w, ypts[1]);
              vertex(xpts[2]+w, ypts[2]);
              vertex(xpts[3]+w, ypts[3]);

              vertex(xpts[0]-w, ypts[0]);
              vertex(xpts[1]-w, ypts[1]);
              vertex(xpts[2]-w, ypts[2]);
              vertex(xpts[3]-w, ypts[3]);
            }
            if ((cr & 12)>0) {
              vertex(xpts[0], ypts[0]+h);
              vertex(xpts[1], ypts[1]+h);
              vertex(xpts[2], ypts[2]+h);
              vertex(xpts[3], ypts[3]+h);

              vertex(xpts[0], ypts[0]-h);
              vertex(xpts[1], ypts[1]-h);
              vertex(xpts[2], ypts[2]-h);
              vertex(xpts[3], ypts[3]-h);
            }

            // I have knowingly retained the small flaw of not
            // completely dealing with the corner conditions
            // (the case in which both of the above are true).
          }
        }
        endShape();
      }
    }
  }

  public void updateGeometry() {
    Gesture J;
    for (int g=0; g<nGestures; g++) {
      if ((J=gestureArray[g]).exists) {
        advanceGesture(J);
      }
    }
  }

  public void advanceGesture(Gesture gesture) 
  {
    // Move a Gesture one step
    if (gesture.exists) 
    { // check
      int nPts = gesture.nPoints;
      int nPts1 = nPts-1;
      Vec3f path[];
      float jx = gesture.jumpDx;
      float jy = gesture.jumpDy;
      if (gesture.waveDirection == 0)
      {
        if (nPts > 0) 
        {
          path = gesture.path;
          for (int i = nPts1; i > 0; i--) 
          {
            path[i].x = path[i-1].x;
            path[i].y = path[i-1].y;
          }
          path[0].x = path[nPts1].x - jx;
          path[0].y = path[nPts1].y - jy;
        }
      } else
      {
        if (nPts > 0 ) {
          path = gesture.path;
          for (int i = 0; i < nPts1; i++) {
            path[i].x = path[i+1].x;
            path[i].y = path[i+1].y;
          }
          path[nPts1].x = path[0].x + jx;
          path[nPts1].y = path[0].y + jy;
        }
      }
      gesture.compile();
    }
  }
  public void clearGestures()
  {
    for (int i = 0; i < nGestures; i++)
    {
      gestureArray[i].clear();
    }
  }
}
class Particle {

  PVector velocity;
  PVector orgVel;
  float lifespan = 255;
  float speed;
  
  PShape part;
  float partSize;
  
  PVector gravity = new PVector(0,0.1f);


  Particle() {
    partSize = random(10,60);
    part = createShape();
    part.beginShape(QUAD);
    part.noStroke();
    part.texture(sprite);
    part.normal(0, 0, 1);
    part.vertex(-partSize/2, -partSize/2, 0, 0);
    part.vertex(+partSize/2, -partSize/2, sprite.width, 0);
    part.vertex(+partSize/2, +partSize/2, sprite.width, sprite.height);
    part.vertex(-partSize/2, +partSize/2, 0, sprite.height);
    part.endShape();
    speed = random(0.5f,2);
    rebirth(width/2,height/2);
    lifespan = random(255);
  }

  public PShape getShape() {
    return part;
  }
  
  public void rebirth(float x, float y) {
    float a = random(TWO_PI);
    speed = random(0.5f,2);
    velocity = new PVector(cos(a), sin(a));
    velocity.mult(speed);
    orgVel = velocity;
    lifespan = 255;   
    part.resetMatrix();
    part.translate(x, y); 
  }
  
  public boolean isDead() {
    if (lifespan < 0) {
     return true;
    } else {
     return false;
    } 
  }
  
  public void updateVelocity(){
    velocity = orgVel;
    velocity.mult(speed);
    orgVel = velocity;
   // velocity.mult(speed);
  }

  public void update() {
    lifespan = lifespan - 1;
    //velocity.add(gravity);
    part.setTint(color(255,lifespan));
    part.translate(velocity.x, velocity.y);
  }
}
class ParticleSystem {
  ArrayList<Particle> particles;

  PShape particleShape;

  ParticleSystem(int n) {
    particles = new ArrayList<Particle>();
    particleShape = createShape(PShape.GROUP);

    for (int i = 0; i < n; i++) {
      Particle p = new Particle();
      particles.add(p);
      particleShape.addChild(p.getShape());
    }
  }

  public void update() {
    for (Particle p : particles) {
      p.update();
    }
  }

  public void setEmitter(float x, float y) {
    for (Particle p : particles) {
      if (p.isDead()) {
        p.rebirth(x, y);
      }
    }
  }
  
  public void setSpeed(float s){
    for(Particle p : particles){
      p.speed = s;
      p.updateVelocity();
    }
  }

  public void display() {
    shape(particleShape);
  }
}


//

 


class PlayState extends State
{

  PranaCircle prana;
  PFont font;
  PFont fontInfo;
  PFont font200;
  ParticleSystem ps;

  CountdownTimer timer;
  String timerCallbackInfo = "";
  int  timeLeft = 2;
  String breathPhase;
  int phaseId = 0;
  String [] breathPhases = new String[]{"Inhalar", "Retener", "Exhalar", "Relajar" };
  int breathCycles = 10;
  int breathCyclesLeft = breathCycles;
  int [] breathPattern = {2, 1, 3, 2};
  //Minim minim;
  //AudioInput in;
  float inputLerp;
  AudioOutput out;
  Oscil wave;

  AudioSample breathIn;
  AudioSample breathOut;
  AudioSample bell;
  AudioSample bowl;

  boolean startGame = false;
  boolean gameOver = false;
  int startCountdown = 3; // coutdown in secs
  int countTime = 3;

  int totalPranaPoints = 0;
  PImage bgImg;

  public void setup()
  {
    
  sprite = loadImage("sprite.png");
  ps = new ParticleSystem(1000);
  prana = new PranaCircle(width/2, height/2, 200);
  bgImg = loadImage("fondo.png");
  font = createFont("spacefri.ttf", 30);
  font200 = createFont("spacefri.ttf", 200);
  fontInfo = loadFont("Consolas-14.vlw");
  textFont(font);
  initAudio();
  initBreathPhase();
  }
  
public void initAudio() {
  //minim = new Minim(app);
  //in = minim.getLineIn();
  bell = minim.loadSample("tibetan_bell.mp3", 512);
  bowl = minim.loadSample("bowl.mp3",512);
  out = minim.getLineOut();
  wave = new Oscil(440, 0.5f, Waves.SINE);
  wave.patch(out);
}

public void initBreathPhase() {
  phaseId = 0;
  breathPhase = breathPhases[phaseId];
  int phaseDuration = breathPattern[phaseId];
  timeLeft = phaseDuration;
  int totalCicleDuration = 0;
  bell.trigger();
  for (int duration : breathPattern) {
    totalCicleDuration += duration;
  }

  long totalDuration = totalCicleDuration*breathCycles*1000+startCountdown*3000;
  //println(breathPhase+":"+timeLeft);
  timer = CountdownTimerService.getNewCountdownTimer(app).configure(1000, totalDuration).start();
}

 
public void breathInfo() {
  //textFont(fontInfo,14);
  fill(255);
  textSize(30);
  text("Wave: "+breathCyclesLeft, width-180, 30);
  textAlign(CENTER);
  text(breathPhase, width/2, height/2+250);
  text(timeLeft, width/2, height/2+280);
  textAlign(LEFT);
}

public void pranaLogic(){
  float inLvl = in.left.level()*50;
  float eMin = 0.5f;
  float eMax = 20;
  float iMin = 0.2f;
  float iMax = 12;
  println(in.left.level()*50);
  if(breathPhase == "Exhalar"){
    if(inLvl > eMin && inLvl < eMax){
      prana.contract();
    }
  }else if(breathPhase == "Inhalar"){
    if(inLvl > iMin && inLvl < iMax){
      prana.expand();
    }
  }
}


public void draw() {
  background(170, 137, 228);
  image(bgImg,0,0);
  fill(255);
  textFont(font, 30);
  text("Pranawave", 30, 30);
 
  if(startGame){
     ps.update();
     ps.display();
     ps.setEmitter(width/2, height/2);
  }
 
  prana.draw();
  breathInfo();
  drawWaveForm();
  
  if(gameOver){
    textFont(font200,100);
    fill(255,200);
    textAlign(CENTER);
    text("GAME OVER", width/2, height/2);
    textAlign(CORNER);
    currentState = new MainMenuState();
    currentState.setup();
  }

  if (!startGame) {
    drawStartCountdown();
  } else {
    pranaLogic();
  }
}

public void drawStartCountdown() {
  textAlign(CENTER, CENTER);
  textFont(font200, 200);
  fill(255);
  text(startCountdown, width/2, height/2-20);
  textAlign(CORNER);
  textFont(font, 30);
}

public void drawWaveForm() {
  // draw the waveforms so we can see what we are monitoring
  stroke(255);
  for (int i = 0; i < in.bufferSize() - 1; i++)
  {
    line( i, (height/2) + in.left.get(i)*20, i+1, (height/2) + in.left.get(i+1)*20);
    //line( i, 150 + in.right.get(i)*50, i+1, 150 + in.right.get(i+1)*50 );
  }
  
  stroke(170, 137, 228);
  strokeWeight(1);
  for (int i = 0; i < out.bufferSize()-1; i++) {
    line( i, (height/2)-50 -out.left.get(i)*100, i+1, (height/2)-50  - out.left.get(i+1)*100 );
    line( i, (height/2)+50 - out.right.get(i)*100, i+1, (height/2)+50 - out.right.get(i+1)*100 );
  }

  fill(255, 100);
  rectMode(CORNERS);
  float lerpIn = lerp(inputLerp, height-50-in.left.level()*400, 0.5f);
  rect(width/2-20, lerpIn, width/2+20, height);

  inputLerp =  height-50-in.left.level()*400;
 

  float amp = in.left.level()*10;
  wave.setAmplitude(amp);

  if (breathPhase == "Inhalar") {
    wave.setFrequency(320);
  } else if ( breathPhase == "Exhalar") {
    wave.setFrequency(110);
  }
}

public void onTickEvent(CountdownTimer t, long timeLeftUntilFinish) {
  //timerCallbackInfo = "[tick] - timeLeft: " + timeLeftUntilFinish + "ms";
  if (!startGame) {
    countTime--;
    if(countTime == 0){
      countTime = 3;
      startCountdown--;
      bell.trigger();
    }
    //startCountdown--;
    if (startCountdown == 0) {
      startGame = true;
      //breathIn.trigger();
    }
  } else {
    timeLeft--;
    if (timeLeft == 0) {
      if (phaseId < 3) {
        phaseId++;
        if (phaseId == 2) {
          //breathOut.trigger();
        }
      } else {
        phaseId = 0;
        //breathIn.trigger();
        breathCyclesLeft--;
      }

      int phaseDuration = breathPattern[phaseId];
      breathPhase = breathPhases[phaseId];
      timeLeft = phaseDuration;
    }
  }
}

public void computePranaPoints(){
  
}

public void onFinishEvent(CountdownTimer t) { 
  gameOver = true;
  bowl.trigger();
}

}


class PranaCircle{

  Circle bgCircle;   // Background circle
  Circle fgCircle;   // Foreground circle
  float maxRadius;
  float minRadius;
  float radius;
  
  PranaCircle(float x, float y, float r){
    bgCircle = new Circle(x, y, r);
    fgCircle = new Circle(x, y, r);
    bgCircle.setFillColor(128, 52, 174);
    bgCircle.hasStroke = true;
    bgCircle.lWeight = 10;
    fgCircle.setFillColor(35, 27, 78);
    maxRadius = r;
    minRadius = 20;
    radius = minRadius;
    fgCircle.r = radius;
  }

  public void draw(){
   bgCircle.draw();
   fill(255,150);
   ellipse(width/2, height/2,100,100);
   fgCircle.draw();
  }
  
  public void expand(){
    println("Radius: " +radius);
    println("Max r: " + maxRadius);
    if(radius < maxRadius){
      radius++;
      fgCircle.r = radius;
    }
  }
  
  public void contract(){
    if(radius > minRadius){
      radius--;
      fgCircle.r = radius;
    }
  }
}

class State
{

  public void setup()
  {
  }
  public void draw()
  {
  }

}

class Vec3f {
  float x;
  float y;
  float p;  // Pressure

  Vec3f() {
    set(0, 0, 0);
  }

  Vec3f(float ix, float iy, float ip) {
    set(ix, iy, ip);
  }

  public void set(float ix, float iy, float ip) {
    x = ix;
    y = iy;
    p = ip;
  }
}
  public void settings() {  size(1024, 768, P2D);  smooth(3); }
  static public void main(String[] passedArgs) {
    String[] appletArgs = new String[] { "--present", "--window-color=#666666", "--stop-color=#cccccc", "GG2" };
    if (passedArgs != null) {
      PApplet.main(concat(appletArgs, passedArgs));
    } else {
      PApplet.main(appletArgs);
    }
  }
}
