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

PImage bin;
int maxSurfaceHeight = 500;
int nativeResoX, nativeResoY;

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;
  public void settings() {
    size(275, 120);
  }

  public void setup() {
    surface.setLocation(285, 240);
    List l = Arrays.asList(availableFx);
    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-105)
      .setSize(15, 15)
      .plugTo(this, "CP5_recordAction");
    rec.getCaptionLabel().getStyle().setMargin(-17, 0, 0, 20);

    Toggle auto = cp5.addToggle("spawn playing")
      .setPosition(100, height-85)
      .setSize(15, 15)
      .setValue(true)
      .plugTo(this, "CP5_spawnPlayingAction");
    auto.getCaptionLabel().getStyle().setMargin(-17, 0, 0, 20);

    Bang pauseAll = cp5.addBang("pause all")
      .setPosition(100, height-65)
      .setSize(15, 15)
      .plugTo(this, "CP5_pauseAllAction");
    pauseAll.getCaptionLabel().getStyle().setMargin(-17, 0, 0, 20);
    
    Bang nativeReso = cp5.addBang("native reso")
      .setPosition(220,height-35)
      .setSize(15,15)
      .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_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 < shaderList.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-18);
    textSize(10);
    line(0, height-120, width, height-120);

    if (pressedBrick != -1) {
      fill(255);
      noStroke();
      rect(mouseX, mouseY, 35, 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);
      println("hi");
      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();
      }
    }

    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);
        }
      }
    }

    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;
        }
      }
    }
    void applyShader() {
      for (int i = 0; i < shaderList.size(); i++) {
        try {
          if (bricks.get(i).active) {
            shaderList.get(i).getValuesFromGUI();
            shaderList.get(i).apply();
          }
        }
        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 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;
      }

      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++) {
        slider.get(i).update();
        params.get(i).value = slider.get(i).value;
      }
      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; //should be private but doesnt work lol
    boolean hovering, hovering_anim_btn, 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);

      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() {



      osci.update();

      if (animated) {
        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;
      }
    }

    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);
    }
  }

  //CP5
  void CP5_listActions(int n) {
    if (shaderList.size() < maxFx) {
      switch(n) {
        case(0):
        shaderManager.addShader(new ASDFPIXELSORT());
        break;
        case(1):
        shaderManager.addShader(new DISTORTER());
        break;
        case(2):
        shaderManager.addShader(new FM());
        break;
        case(3):
        shaderManager.addShader(new WZIP());
        break;
        case(4):
        shaderManager.addShader(new AUECHO());
        break;
        case(5):
        shaderManager.addShader(new SLITSCAN());
        break;
        case(6):
        shaderManager.addShader(new WAHWAH());
        break;
        case(7):
        shaderManager.addShader(new PHASER());
        break;
        case(8):
        shaderManager.addShader(new ECHO());
        break;
        case(9):
        shaderManager.addShader(new DARKER());
        break;
        case(10):
        shaderManager.addShader(new BRIGHTER());
        break;
        case(11):
        shaderManager.addShader(new AMPLIFY());
        break;
        case(12):
        shaderManager.addShader(new BROKENCOLORROT());
        break;
        case(13):
        shaderManager.addShader(new POSTER());
        break;
        case(14):
        shaderManager.addShader(new DUAL());
        break;
        /*
         case(4):
         shaderManager.addShader(new AUECHO());
         break;
         */
      default:
        break;
      }
    }
  }

  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;
    }
    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).active = !bricks.get(i).active;
      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) {
          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 (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);
  }
}
