//contains the GUI (ie CP5) in the secondapplet. also contains 3 classes: shaderManager, Brick and Slider

/* todo: implement "solo" and "only up till here" button on bricks */

PImage bin;
int maxSurfaceHeight = 500;
int nativeResoX, nativeResoY;
boolean renderSizeChanged = true;

public class SecondApplet extends PApplet {
  ControlP5 cp5;
  shaderManager shaderManager = new shaderManager();
  ArrayList<Shader> shaderList = new ArrayList<Shader>();
  ArrayList<Brick> bricks = new ArrayList<Brick>();
  ArrayList<Slider> slider = new ArrayList<Slider>(); //brick
  int binX, binY, binS;

  //PApplet parent;
  /*SecondApplet(PApplet _parent){
   super();
   this.parent = _parent;
   }*/

  public void settings() {
    size(275, 120);
  }
  //Toggle webcam, auto;
  Toggle auto;
  public void setup() {
    surface.setLocation(285, 240);
    //List l = Arrays.asList(availableFx);
    List l = Arrays.asList(FX);
    cp5 = new ControlP5(this);
    cp5.addScrollableList("")
      .setPosition(10, height-110)
      .setSize(80, 100)
      .setBarHeight(0)
      .setItemHeight(20)
      .plugTo(this, "CP5_listActions")
      .addItems(l)
      .setType(ControlP5.LIST);


    Toggle rec = cp5.addToggle("record")
      .setPosition(100, height-110)
      .setSize(12, 12)
      .plugTo(this, "CP5_recordAction");
    rec.getCaptionLabel().getStyle().setMargin(-17, 0, 0, 20);

    auto = cp5.addToggle("spawn playing")
      .setPosition(100, height-95)
      .setSize(12, 12)
      .setValue(false)
      .plugTo(this, "CP5_spawnPlayingAction");
    auto.getCaptionLabel().getStyle().setMargin(-17, 0, 0, 20);


    Bang pauseAll = cp5.addBang("pause all")
      .setPosition(100, height-80)
      .setSize(12, 12)
      .plugTo(this, "CP5_pauseAllAction");
    pauseAll.getCaptionLabel().getStyle().setMargin(-17, 0, 0, 20);



    /* webcam = cp5.addToggle("webcam")
     .setPosition(100, height-65)
     .setSize(12, 12)
     .setValue(false)
     .plugTo(this, "CP5_webcamAction");
     webcam.getCaptionLabel().getStyle().setMargin(-17, 0, 0, 20);*/

    Toggle robot = cp5.addToggle("autoPlay")
      .setPosition(100, height-50)
      .setSize(12, 12)
      .setValue(false)
      .plugTo(this, "CP5_robotAction");
    robot.getCaptionLabel().getStyle().setMargin(-17, 0, 0, 20);


    Bang nativeReso = cp5.addBang("native reso")
      .setPosition(220, height-35)
      .setSize(12, 12)
      .plugTo(this, "CP5_nativeResoAction");
    nativeReso.getCaptionLabel().getStyle().setMargin(-17, 0, 0, 20);

    /*
    Toggle saveParams = cp5.addToggle("save parameters")
     .setPosition(100, height-45)
     .setSize(15, 15)
     .plugTo(this, "CP5_saveParamsAction");
     saveParams.getCaptionLabel().getStyle().setMargin(-17,0,0,20);
     */


    binX = width-55;
    binY = height-85;
    binS = 40;
  }

  void CP5_robotAction()  {
    startRobot = true;
    autoPlay = !autoPlay;
  }
  /*
  void CP5_webcamAction()  {
   webcamMode = !webcamMode;
   if (webcamMode) sourceManager.startWebcam();
   else {
   sourceManager.stopWebcam();
   source = null; //to trigger auto-fallback next frame
   }
   }
   */
  void CP5_nativeResoAction() {
    renderSize = max(nativeResoX, nativeResoY);
    //renderer.setSize(nativeResoX, nativeResoY);
  }
  void CP5_recordAction() {
    recording = !recording;
  }

  void CP5_spawnPlayingAction() {
    spawnPlaying = !spawnPlaying;
  }


  void CP5_pauseAllAction() {
    for (int i = 0; i < bricks.size(); i++) {
      if (!bricks.get(i).paused) bricks.get(i).playPause();
    }
  }


  int totalBrickHeight;
  public void draw() {

    background(68, 68, 68);
    pushMatrix();
    if (heightOverLimit) {
      translate(0, scrollAmount);
    }
    totalBrickHeight = 0;
    for (int i = 0; i < bricks.size(); i++) {
      bricks.get(i).update();
      bricks.get(i).display();
      //bricks.get(i).y = totalBrickHeight; ????? what was that for
      totalBrickHeight+= bricks.get(i).h;
    }
    popMatrix();
    if (heightOverLimit) {
      strokeWeight(3);
      fill(255);
      line(width-5, -scrollAmount+4, width-5, height-120+(maxSurfaceHeight-120-totalBrickHeight)-scrollAmount-4);
      //-scrollAmount + (-1*(maxSurfaceHeight-120-totalBrickHeight)));
    }
    fill(68, 68, 68);
    noStroke();
    rect(0, height-120, width, 120);
    image(bin, binX, binY, binS, binS);
    stroke(255);
    strokeWeight(1);
    textSize(25);
    fill(255);
    text(renderer.width + "x" + renderer.height, 100, height-15);
    if (typingNumber) {
      rect(100, height - 35, textWidth(str(renderer.width) + "x" + str(renderer.height)), 25);
      fill(0);
      text(newResoString, 100, height-15);
      fill(255);
      if (frameCount >= numberTypedFrame+90) {
        setTypedReso();
      }
    }
    textSize(10);
    line(0, height-120, width, height-120);

    if (pressedBrick != -1) {
      fill(255);
      noStroke();
      rect(mouseX, mouseY, 35, 15);
    }
    
    
    //audioindicator
    fill(map(volume,0,0.5,0,255));
    noStroke();
    ellipse(260,height-105,15,15);
  }

  boolean heightOverLimit;

  void refreshSurface(int mode) {
    int currentBrickHeight = 0;
    if (shaderList.size() > 0) {
      if (mode == 0) {
        currentBrickHeight = totalBrickHeight + bricks.get(shaderList.size()-1).h;
        surface.setSize(width, height+bricks.get(shaderList.size()-1).h);
      } else if (mode == 1) {
        currentBrickHeight = totalBrickHeight - bricks.get(pressedBrick).h;
        if (heightOverLimit) {
          surface.setSize(width, currentBrickHeight+120);
        } else {
          surface.setSize(width, height-bricks.get(pressedBrick).h);
        }
      }
    } else {
      surface.setSize(width, 120);
    }
    if (currentBrickHeight + 120 > maxSurfaceHeight) {
      surface.setSize(width, maxSurfaceHeight);
      heightOverLimit = true;
    } else {
      heightOverLimit = false;
      scrollAmount = 0;
    }
    cp5.setPosition(0, height-120);
    binY = height-87;
  }

  class shaderManager {

    void addShader(Shader shader) {
      shaderList.add(shader);
      shader.pos = shaderList.size()-1;
      bricks.add(new Brick(shaderList.size()-1, shaderList.get(shaderList.size()-1).params, shader.name));
      refreshSurface(0);
      //if (!spawnPlaying) bricks.get(shaderList.size()-1).pauseAllSliders();
      println("Added " + shader + " to fxList at spot. List length: " + shaderList.size());
    }

    void swapShader(int pos1, int pos2) {
      Collections.swap(shaderList, pos1, pos2);
      Collections.swap(bricks, pos1, pos2);
      shaderList.get(pos1).pos = pos1;
      shaderList.get(pos2).pos = pos2;
      bricks.get(pos1).setPos(pos1);
      bricks.get(pos2).setPos(pos2);
      for (int i = 0; i < bricks.size(); i++) {
        bricks.get(i).updateSliderPos();
      }
      for (int i = min(pos1, pos2); i < shaderList.size(); i++) {
        //println(i);
        //shaderList.get(i).change = true;
      }
    }

    void moveShader(int pos1, int pos2) {
      if (pos2 > pos1) {
        pos2 -=1;
        for (int i = pos1; i < pos2; i++) {
          swapShader(i, i+1);
        }
      } else {
        for (int i = pos1; i > pos2; i--) {
          swapShader(i, i-1);
        }
      }
      for (int i = max(pos1, pos2); i > 0; i--) {
        //println(i);
        //shaderList.get(i).change = true;
      }
    }

    void removeShader(int pos) {
      if (pos >= 0 && shaderList.size() > pos) {
        refreshSurface(1);
        shaderList.remove(pos);
        bricks.remove(pos);
        for (int i = shaderList.size()-1; i > -1; i--) {
          bricks.get(i).setPos(i);
          bricks.get(i).updateSliderPos();
          shaderList.get(i).pos = i;
        }
      }
      for (int i = pos; i < shaderList.size(); i++) {
        //shaderList.get(i).change = true;
      }
    }


    int previousRenderSize = renderSize;
    void applyShader() {
      renderSizeChanged = true;
      if (previousRenderSize != renderSize)
        previousRenderSize = renderSize;
      else
        renderSizeChanged = false;

      for (int i = 0; i < shaderList.size(); i++) {
        try {

          shaderList.get(i).getValuesFromGUI();
          if (bricks.get(i).active) {
            //shaderList.get(i).getValuesFromGUI();
            shaderList.get(i).shade();
          }
        }
        catch(Exception e) {
          println("Warning: Brick not yet loaded");
        }
        if (i > maxFx) break;
      }
    }
  }

  int pressedBrick = -1;
  class Brick {
    ArrayList<Slider> slider = new ArrayList<Slider>();
    ArrayList<Param> params = new ArrayList<Param>();
    boolean active, paused, dragging, dragHovering, hovering, hoveringLineAbove, hoveringLineBelow, hoverActiveButton, hoverPauseButton;
    boolean[] pauseStates;
    color brickBGcol;
    int pos;
    int h, y;
    String name;

    Brick(int pos_, ArrayList params_, String name_) {
      name = name_;
      pos = pos_;
      params = params_;
      active = true;
      y = totalBrickHeight;
      h = 20 + params.size() * 20;
      for (int i = 0; i < params.size(); i++) {
        slider.add(new Slider(10, 20+(20*i)+y, 230, 12, params.get(i).minValue, params.get(i).maxValue, params.get(i).name, params.get(i).type, params.get(i).osciModes)); //add possible waves to slider
      }
      pauseStates = new boolean[slider.size()];
      for (int i = 0; i < pauseStates.length; i++) {
        pauseStates[i] = false;
      }
      if (!spawnPlaying) {
        playPause();
      }
    }

    void setPos(int pos_) {
      pos = pos_;
    }

    void updateSliderPos() {
      y = 0;
      for (int i = 0; i < pos; i++) {
        y += bricks.get(i).h;
      }
      for (int i = 0; i < slider.size(); i++) {
        slider.get(i).y = 20+(20*i)+y;
      }
    }

    void exchangeSlider(int sliderNo, Param param) {
      slider.get(sliderNo).setMinMax(param.minValue, param.maxValue);
      slider.get(sliderNo).setMode(param.type);
      slider.get(sliderNo).setLabel(param.name);
    }

    void playPause() {
      paused = !paused;
      if (paused) {
        pauseAllSliders();
      } else {
        resumeAllSliders();
      }
    }
    void toggleActive() {
      active = !active;

      //for (int i = pos; i < shaderList.size(); i++) {
      //   shaderList.get(i).change = true;
      //}
    }

    void pauseAllSliders() {
      for (int i = 0; i < slider.size(); i++) {
        pauseStates[i] = slider.get(i).animated;
        slider.get(i).animated = false;
      }
    }

    void resumeAllSliders() {
      for (int i = 0; i < slider.size(); i++) {
        slider.get(i).animated = pauseStates[i];
      }
    }

    void update() {
      boolean sliderHover = false;
      for (int i = 0; i < slider.size(); i++) {
        if (slider.get(i).hovering || slider.get(i).hovering_anim_btn) sliderHover = true;
        if (paused && slider.get(i).animated) paused = false;
        
        params.get(i).audioMulti = slider.get(i).audioVal * volume * 2;
        
      }
      

      if (mouseX > 10 && mouseX < 22 && mouseY > 4+totalBrickHeight+scrollAmount && mouseY < 4+totalBrickHeight+13+scrollAmount) {
        hoverActiveButton = true;
      } else {
        hoverActiveButton = false;
      }

      if (mouseX > 25 && mouseX < 37 && mouseY > 4+totalBrickHeight+scrollAmount && mouseY < 4+totalBrickHeight+13+scrollAmount) {
        hoverPauseButton = true;
      } else {
        hoverPauseButton = false;
      }

      if (mouseY > y-5+scrollAmount && mouseY < y+5+scrollAmount && pressedBrick != -1) {
        hoveringLineAbove = true;
        if (pos > 0) {
          bricks.get(pos-1).hovering = false;
        }
      } else if (pos == bricks.size()-1 && mouseY > y+h-5+scrollAmount && mouseY < y+h+5+scrollAmount && pressedBrick != -1) {
        hoveringLineBelow = true;
      } else {
        hoveringLineBelow = false;
        hoveringLineAbove = false;
        if (mouseY > totalBrickHeight+scrollAmount && mouseY < totalBrickHeight+h+scrollAmount && !sliderHover && !hoverActiveButton && !hoverPauseButton) {
          hovering = true;
        } else {
          hovering = false;
        }
      }

      for (int i = 0; i < slider.size(); i++) {
        //params.get(i).value = slider.get(i).value;
        
        //* map(volume,0,1,0.8,1.2);
        //slider.get(i).value = map(volume,0,1,0.8,1.2) * slider.get(i).value;
        params.get(i).value = slider.get(i).value;
        slider.get(i).update();
      }
      brickBGcol = color(85, 85, 85);
    }

    void press() {
      pressedBrick = pos;
    }


    void display() {
      noStroke();
      if (active) {
        if (dragging) brickBGcol = color(51, 51, 51);
        else if (dragHovering && !hoveringLineBelow && !hoveringLineAbove) brickBGcol= color (105, 105, 105);
        else brickBGcol = color(85, 85, 85);
      } else {
        brickBGcol = color(0);
      }
      fill(brickBGcol);
      rect(0, totalBrickHeight, width, h);

      fill(255);
      text(name, 40, 13+totalBrickHeight, 10);

      stroke(255);
      strokeWeight(1);
      if (active) fill(185);
      else noFill();
      rect(10, 4+totalBrickHeight, 10, 10);

      if (paused) fill(185);
      else noFill();
      rect(25, 4+totalBrickHeight, 10, 10);

      for (int i = 0; i < slider.size(); i++) {
        slider.get(i).display();
      }

      stroke(150);
      strokeWeight(1);
      line(0, totalBrickHeight+h-1, width, totalBrickHeight+h-1);

      if (hoveringLineAbove) {
        stroke(200);
        strokeWeight(5);
        line(0, y, width, y);
      }
      if (hoveringLineBelow) {
        stroke(200);
        strokeWeight(5);
        line(0, y+h, width, y+h);
      }
    }
  }

  int pressedSlider = -1;

  //class Slider extends Brick{
  class Slider {
    int x, y, w, h, mode;
    int[] osciModes;
    float minVal, maxVal, value, audioVal; //should be private but doesnt work lol
    boolean hovering, hovering_anim_btn,hovering_audio_slider, animated;
    String label;
    Oscillator osci;

    Slider(int x_, int y_, int w_, int h_, float minVal_, float maxVal_, String label_, int mode_, int[] osciModes_) {
      x = x_;
      y = y_;
      w = w_;
      h = h_;
      minVal = minVal_;
      maxVal = maxVal_;
      mode = mode_;
      label = label_ + (mode == INTVAL ? " (INT)" : " (FLOAT)");
      osciModes = osciModes_;

      osci = new Oscillator(int(random(2, 5)), osciModes);
      osci.amplitude(minVal, maxVal);
      osci.update();
      //setValue(osci.value);
      setValue((minVal+maxVal)/2);

      animated = true;

      slider.add(this);
    }

    void setPosX(int x_) {
      x = x_;
    }

    void setMinMax(float min, float max) {
      float cval = map(value, minVal, maxVal, 0, 10000);
      minVal = min;
      maxVal = max;
      osci.amplitude(minVal, maxVal);
      setValue(map(cval, 0, 10000, minVal, maxVal));
    }
    void setLabel(String lab) {
      label = lab + (mode == INTVAL ? " (INT)" : " (FLOAT)");
    }
    void setMode(int mode_) {
      mode = mode_;
    }

    void press(int myId_) {
      pressedSlider = myId_;
      animated = false;
      setValue(map(mouseX, x, x+w, minVal, maxVal));
    }

    void setValue(float value_) {
      value = value_;
      value = constrain(value, minVal, maxVal);
      if (mode == INTVAL) value = int(round(value));
    }

    float getValue() {
      return(value);
    }

    void update() {

      //float audioImpact = (audioVal * volume) + 1;
      //setValue(value*(1+audioVal));

     // osci.update();

      if (animated) {

        osci.update();
        setValue(osci.value);
      }
      

      if (mouseX >= x && mouseX <= x+w && mouseY >= y+scrollAmount && mouseY <= y+h+scrollAmount) {
        hovering = true;
      } else {
        hovering = false;
      }

      if (mouseX >= x+w+5 && mouseX <= x+w+5+15 && mouseY >= y+scrollAmount && mouseY <= y+h+scrollAmount) {
        hovering_anim_btn = true;
      } else {
        hovering_anim_btn = false;
      }
      
      if (mouseX >= x+w+20 && mouseX <= x+w+35 && mouseY >= y+scrollAmount && mouseY <= y+h+scrollAmount) {
        hovering_audio_slider = true;
      } else {
        hovering_audio_slider = false;
      }
    }

    void display() {

      stroke(255);
      strokeWeight(1);
      fill(100+(40*int(hovering)));
      rect(x, y, w, h);
      fill(185+(40*int(hovering)));
      rect(x, y, map(value, minVal, maxVal, 0, w), h);
      fill(255);
      text(label, x+5, y+10);

      if (animated) {
        fill(185+(40*int(hovering_anim_btn)));
      } else {
        fill(100+(40*int(hovering_anim_btn)));
      }
      rect(x+w+5, y, h, h);
      
      stroke(255);
      line(x+w+5+h+10,y, x+w+5+h+10, y+h);
      
      line(x+w+5+h+5, y+h-(audioVal*h), x+w+5+h+15, y+h-(audioVal*h));
    }
  }

  //CP5
  void CP5_listActions(int n) {
    addByID(n);
  }

  boolean startRobot;
  void robot() {
    if (startRobot) {
      if (!spawnPlaying) {
        auto.setValue(true);
      }
    }
    startRobot = false;
    if (renderFrameRate > 5) {
      renderSize+=2;
    } else {
      if (renderSize >= 45) renderSize-=15;
    }

    if (shaderList.size() == 0) {
      println("adding");
      addByID((int)random(FX.length));
    }
    if (random(1)<0.01) {
      println("adding");
      addByID((int)random(FX.length));
    }

    if (random(1)<0.01) {
      int removeMe = int(random(shaderList.size()-1));
      if ((removeMe == 0 && bricks.get(removeMe).name.equals("Webcam")) == false) {
        println(bricks.get(removeMe).name);
        println("removing" + removeMe);
        bricks.get(removeMe).press();
        shaderManager.removeShader(removeMe);
        pressedBrick = -1;
      }
    }
  }

  void addByName(String name) {
    Class<?> shaderChildClass = null;
    Shader s = null;
    try {
      shaderChildClass = Class.forName(sketchClass.getName() + "$" + name);
      s = (Shader) shaderChildClass.getDeclaredConstructor(sketchClass).newInstance(mainApplet);
      shaderManager.addShader(s);
    }
    catch (final ReflectiveOperationException ex) {
      System.err.println(ex);
    }
  }

  void addByID(int id) {
    addByName(FX[id]);
  }

  void mousePressed() {
    for (int i = slider.size()-1; i >= 0; i--) {
      Slider s = slider.get(i);
      if (s.hovering) s.press(i);
      if (s.hovering_anim_btn) s.animated = !s.animated;
      if (s.hovering_audio_slider){
        s.audioVal = map(mouseY-(s.y+scrollAmount),0,12,1,0);
      }
    }
    for (int i = bricks.size()-1; i >= 0; i--) {
      if (bricks.get(i).hovering) {
        bricks.get(i).press();
      }
      if (bricks.get(i).hoverActiveButton)
      {
        bricks.get(i).toggleActive();
      }
      if (bricks.get(i).hoverPauseButton) bricks.get(i).playPause();
    }
  }
  void mouseDragged() {
    if (pressedSlider != -1) {
      Slider s = slider.get(pressedSlider);
      s.setValue(map(mouseX, s.x, s.x+s.w, s.minVal, s.maxVal));
    }
    if (pressedBrick != -1) {
      bricks.get(pressedBrick).dragging = true;

      for (int i = 0; i < bricks.size(); i++) {
        if (pressedBrick != i && bricks.get(i).hovering && mouseY < height-120) {
          bricks.get(i).dragHovering = true;
        } else {
          bricks.get(i).dragHovering = false;
        }
      }
    }
  }

  void mouseReleased() {
    if (pressedBrick != -1) {
      bricks.get(pressedBrick).dragging = false;

      for (int i = 0; i < bricks.size(); i++) {
        bricks.get(i).dragHovering = false;
        if (mouseY < height-120) {
          if (pressedBrick != i) {
            if (bricks.get(i).hovering && !bricks.get(i).hoveringLineAbove && !bricks.get(i).hoveringLineBelow) {
              shaderManager.swapShader(pressedBrick, i);
            }

            if (pressedBrick != i-1 && bricks.get(i).hoveringLineAbove) {
              shaderManager.moveShader(pressedBrick, i);
              break;
            }
            if (bricks.get(i).hoveringLineBelow) {
              shaderManager.moveShader(pressedBrick, bricks.size()); //move to last
            }
          }
        }
      }

      if (mouseX > binX && mouseX < binX+binS && mouseY > binY && mouseY < binY+binS) {
        println("removing");
        shaderManager.removeShader(pressedBrick);
      }
    }
    pressedSlider = -1;
    pressedBrick = -1;
  }
  int scrollAmount;
  void mouseWheel(MouseEvent event) {
    textSize(25);
    if (mouseX > 100 && mouseX < textWidth(str(renderer.width) + "x" + str(renderer.height)) + 100 && mouseY > height - 40 && mouseY < height) {
      if (event.getCount() < 0) renderSize += 8;
      else renderSize -= 8;
      renderSize = constrain(renderSize, 8, 15000);
    }
    if (heightOverLimit && mouseX > 0 && mouseX < width && mouseY > 0 && mouseY < height-120) {
      for (int i = 0; i < bricks.size(); i++) {
        scrollAmount -= event.getCount();
        scrollAmount = constrain(scrollAmount, maxSurfaceHeight-120-totalBrickHeight, 0);
        //bricks.get(i).y += event.getCount();
        //bricks.get(i).updateSliderPos();
      }
    }
    textSize(10);
    
    if (mouseX > 245 && mouseY < height-90 && mouseY > height-120) {
      volumeRoof -= float(event.getCount())/100;
      if(volumeRoof > 100) volumeRoof = 100;
      if(volumeRoof <= 0.01) volumeRoof = 0.01;
    }
  }
  int numberTypedFrame;
  boolean typingNumber;
  String newResoString;
  void keyPressed() {
    if (keyIsNumber(key) || key == BACKSPACE) {
      numberTypedFrame = frameCount;
      if (!typingNumber) {
        typingNumber = true;
        newResoString = "";
      }
      if (keyIsNumber(key)) newResoString += str(key);
      else if ((newResoString != null) && (newResoString.length() > 0)) newResoString = newResoString.substring(0, newResoString.length() - 1);
    }
    if (typingNumber && key == ENTER) {
      setTypedReso();
    }
  }

  void setTypedReso() {
    typingNumber = false;
    if (newResoString != "") {
      renderSize = constrain(int(newResoString), 8, 15000);
    }
  }
  boolean keyIsNumber(int k) {
    if (k == '0' ||k == '1' || k == '2' || k == '3' || k == '4' || k == '5' || k == '6' || k == '7' || k == '8' || k == '9') {
      return true;
    } else {
      return false;
    }
  }
}
