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

import controlP5.*; 
import java.util.LinkedList; 

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

/*
Mal-o-Mat is a painting software, written in Processing
2017 by Pit Noack for c't magazin, issue 24/2017
Download fee-based article: <http://ct.de/ymqc>
the development of future versions will be documented here: <http://www.maschinennah.de/>
 
Mal-o-Mat is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
 
Mal-o-Mat is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details:
<http://www.gnu.org/licenses/>
 
@author   Pit Noack <mail@pitnoack.de>
@modified 03/11/2017
@version  2.0
*/



PApplet app = this;
ControlP5 cp5;

int kontrollfeldBreite = 180;
int randBreite = 5;

int dXBild = kontrollfeldBreite;
int dYBild = randBreite;
int dXVorschau = randBreite;
int dYVorschau = randBreite;

Pinsel aktuellerPinsel;
RadierPinsel radierPinsel;
LinienPinsel linienPinsel;
SpruehPinsel spruehPinsel;

Bild bild;
PinselVorschau vorschau;

public void setup() {
  surface.setResizable(true);
  surface.setSize(300, 230);
  colorMode(RGB, 100);
  background(90);
  initStartKontrollelemente();
}

public void draw() {
  background(90);
  if (bild == null) {
    return;
  }
  if (mousePressed) {
    aktuellerPinsel.zeichnen();
  }
  bild.anzeigen();
  vorschau.anzeigen();
  fill(0);
  textSize(11);
  text("Pinselvorschau", randBreite, 120);
  text("'m' --> Tastaturk\u00fcrzel anzeigen", randBreite, height - 10);
}



public void mouseReleased() {
  if (bild == null) {
    return;
  }
  if (bild.wirdBearbeitet == true) {
    bild.malenBeenden();
  }
  if (vorschau.wirdBearbeitet == true) {
    vorschau.malenBeenden();
  }
}

public void keyPressed() {
  if (bild == null) {
    return;
  }
  loop();
  switch(key) {
  case '1':
    pinselWaehlen(linienPinsel);
    break;
  case '2':
    pinselWaehlen(spruehPinsel);
    break;
  case '0':
    pinselWaehlen(radierPinsel);
    break;
  case 'z':
    bild.undo();
    break;
  case 's':
    bild.speichern();
    break;
  case 'o':
    bild.oeffnen();
    break;
  case 'l':
    bild.loeschen();
    break;
  case 'r':
    reset();
    break;
  case 'm':
    noLoop();
    tastaturkuerzelAnzeigen();
    break;
  default:
    break;
  }
}
public class Bild {

  PGraphics pg;
  UndoSpeicher undoSpeicher;
  boolean wirdBearbeitet = false;
   
  Bild(int breite, int hoehe) {
    pg = createGraphics(breite, hoehe);
    pg.beginDraw();
    pg.colorMode(RGB, 100);
    pg.background(100);
    pg.endDraw();
    undoSpeicher = new UndoSpeicher(this);
  }
  
  public void anzeigen() {
    image(pg, dXBild, dYBild);
  }
  
  public void malenBeenden(){
    wirdBearbeitet = false;
    undoSpeicher.save();
  }
  
  public void bildGroesseAendern(int breite, int hoehe) {
    PGraphics pgNeu = createGraphics(breite, hoehe);
    pgNeu.beginDraw();
    pgNeu.colorMode(RGB, 100);
    pgNeu.background(100);
    pgNeu.image(pg, 0, 0);
    pgNeu.endDraw();
    pg = pgNeu;
    aktualisiereFensterGroesse();
  }
  
  public void undo(){
    PImage letztesBild = undoSpeicher.undo();
    bildGroesseAendern(letztesBild.width, letztesBild.height);
    pg.beginDraw();
    pg.image(letztesBild, 0, 0);
    pg.endDraw(); 
  }
  
  public void loeschen() {
    pg.beginDraw();
      pg.fill(100);
      pg.noStroke();
      pg.rect(0, 0, pg.width, pg.height);
    pg.endDraw();
    malenBeenden();   
  }
  
  public boolean mausImBild(){
    return mouseX > dXBild
    && mouseX <  bild.pg.width + dXBild
    && mouseY > dYBild
    && mouseY < dYBild +  bild.pg.height;
  }

  public void speichern() {
    selectOutput("Bild Speichern:", "speichernAbschliessen", null, this);
  }

  public void speichernAbschliessen(File file) {
    if (file != null) {
      pg.save(file.getPath());
    }
  }
  
  public void oeffnen() {
    selectInput("Bild \u00d6ffnen:", "oeffnenAbschliessen", null, this);
  }

  public void oeffnenAbschliessen(File file) {
    if (file != null) {
      PImage geladenesBild = loadImage(file.getPath());
      bildGroesseAendern(geladenesBild.width, geladenesBild.height);
      pg.beginDraw();
      pg.image(loadImage(file.getPath()), 0, 0);
      pg.endDraw();
      undoSpeicher.save();
    }
  }
  
}
abstract class FarbPinsel extends Pinsel {
 
  FarbPinsel() {
    super();
    cp5.addSlider("rot");
    cp5.addSlider("gruen");
    cp5.addSlider("blau");
  }

  public void zeichnen() {
    super.zeichnen();
    pg.beginDraw();
    pg.stroke(cp5.getValue("rot"), cp5.getValue("gruen"), cp5.getValue("blau"));
    pg.endDraw();
  }
  
}
class LinienPinsel extends FarbPinsel {

  LinienPinsel() {
    super();
    label.setText(" LINIENPINSEL");
  }

  public void zeichnen() {
    super.zeichnen();
    if ( !pinselMalt() ) {
      return;
    }
    pg.beginDraw(); 
    pg.strokeWeight(radius());
    pg.line(pmouseX + dX, pmouseY + dY, mouseX + dX, mouseY + dY);
    pg.endDraw();
  }
}
abstract class Pinsel {
 
  ControlP5 cp5;
  PGraphics pg;
  int dX, dY;
  Textlabel label;

  Pinsel() {
    cp5 = new ControlP5(app);
    cp5.setPosition(0, 140);
    cp5.setAutoAddDirection(ControlP5.VERTICAL);
    cp5.setColorCaptionLabel(0);

    label = cp5.addTextlabel("label");
    label.setColorValue(0xff000000);
    label.setFont(createFont("Arial", 15));
    cp5.addSlider("radius", 0.1f, 1).setValue(0.3f);

    cp5.setVisible(false);
    pg = bild.pg;
  }

  public float radius() {
    return 100 * pow(cp5.getValue("radius"), 3);
  }

  public boolean pinselMalt() {
    return bild.wirdBearbeitet || vorschau.wirdBearbeitet;
  }

  public void zeichnen() {
    if (bild.mausImBild() && !vorschau.wirdBearbeitet) {
      bild.wirdBearbeitet = true;
      pg = bild.pg;
      dX = -dXBild;
      dY = -dYBild;
    } else if (vorschau.mausImBild() && !bild.wirdBearbeitet) {
      vorschau.wirdBearbeitet = true;
      pg = vorschau.pg;
      dX = -dXVorschau;
      dY = -dYVorschau;
    }
  }

  public void aktiv(boolean aktiv) {
    cp5.setVisible(aktiv);
  }
}
class PinselVorschau {
  
  PGraphics pg;
  boolean wirdBearbeitet = false;
  
  PinselVorschau() {
    pg = createGraphics(kontrollfeldBreite - 2 * randBreite, 100);
    pg.beginDraw();
    pg.colorMode(RGB, 100);
    pg.background(100);
    pg.endDraw();
  }
  
  public void anzeigen() {
    image(pg, dXVorschau, dYVorschau);
  }
  
  public void malenBeenden() {
    wirdBearbeitet = false;
  }
  
  public boolean mausImBild(){
    return mouseX > dXVorschau
    && mouseX <  vorschau.pg.width + dXVorschau
    && mouseY > dYVorschau
    && mouseY < dYVorschau +  vorschau.pg.height;
  }
  
}
class RadierPinsel extends Pinsel {
  RadierPinsel() {
    super();
    label.setText("RADIERER");
  }

  public void zeichnen() {
    super.zeichnen();
    if ( !pinselMalt()) {
      return;
    }
    pg.beginDraw();
    pg.strokeWeight(radius());
    pg.stroke(100);
    pg.line(mouseX + dX, mouseY + dY, pmouseX + dX, pmouseY + dY);
    pg.endDraw();
  }
}
class SpruehPinsel extends FarbPinsel {

  SpruehPinsel() {
    super();
    cp5.addSlider("dichte", 0.1f, 1);
    label.setText(" SPR\u00dcHPINSEL");
  }

  public void zeichnen() {
    super.zeichnen();
    if ( !pinselMalt()) {
      return;
    }
    pg.beginDraw();
    int nPunkte = (int)
      (cp5.getValue("radius") * cp5.getValue("dichte") * 200.0f);
    for (int i = 0; i < nPunkte; ++i) {
      float r = pow(cp5.getValue("radius"), 3) * 30.0f;
      float x = randomGaussian() * r;
      float y = randomGaussian() * r;
      pg.strokeWeight(0.5f);
      pg.point(mouseX + x + dX, mouseY + y + dY);
    }
    pg.endDraw();
  }
}


class UndoSpeicher {
  
  LinkedList<PImage> img;
  PImage aktuellesBild;
  static final int nBilder = 20;
  
  UndoSpeicher(Bild bild) {
    img = new LinkedList<PImage>();
    aktuellesBild = bild.pg.copy();
  }
  
  public void save() {
    img.add(aktuellesBild);
    aktuellesBild = bild.pg.copy();
    if (img.size() > nBilder) {
      img.pollFirst();
    }
  }
  
  public PImage undo() {
    if (!img.isEmpty()) {
      aktuellesBild = img.pollLast();
    }
    return aktuellesBild;
  }
}
public void initStartKontrollelemente() {
  cp5 = new ControlP5(this);

  cp5.setColorCaptionLabel(0);
  cp5.setFont(createFont("Arial", 12));
  cp5.setColorBackground(0xffFFFFFF);

  cp5.addTextlabel("label")
    .setPosition(70, 20)
    .setColorValue(0xff000000)
    .setFont(createFont("Arial", 20))
    .setText("c't Mal-o-Mat 2.0");

  cp5.addTextfield("breite")
    .setPosition(70, 60)
    .setSize(70, 30)
    .setColor(0xff000000)
    .setInputFilter(ControlP5.INTEGER)
    .setValue("640");

  cp5.addTextfield("hoehe")
    .setPosition(160, 60)
    .setSize(70, 30)
    .setColor(0xff000000)
    .setInputFilter(ControlP5.INTEGER)
    .setValue("480");

  cp5.addButton("initNeu")
    .setLabel("Neues Bild")
    .setPosition(70, 120)
    .setSize(160, 30)
    .setFont(createFont("Arial", 12));

  cp5.addButton("initMitBildDatei")
    .setLabel("Bild laden")
    .setPosition(70, 170)
    .setSize(160, 30)
    .setFont(createFont("Arial", 12));
}

public void initNeu() {
  initialisiere();
  aktualisiereFensterGroesse();
}

public void initMitBildDatei() {
  initialisiere();
  bild.oeffnen();
}

public void initialisiere() {
  cp5.setVisible(false);
  int breite = Integer.parseInt(((Textfield)(cp5.getController("breite"))).getText());
  int hoehe = Integer.parseInt(((Textfield)(cp5.getController("hoehe"))).getText());

  bild = new Bild(breite, hoehe);
  vorschau = new PinselVorschau();

  radierPinsel = new RadierPinsel();
  linienPinsel = new LinienPinsel();
  spruehPinsel = new SpruehPinsel();

  aktuellerPinsel = linienPinsel;
  aktuellerPinsel.aktiv(true);
}

public void aktualisiereFensterGroesse() {
  int breite = kontrollfeldBreite + bild.pg.width + randBreite;
  int hoehe = bild.pg.height + 2 * randBreite;
  hoehe = constrain(hoehe, 480, MAX_INT);
  surface.setSize(breite, hoehe);
}

public void reset() {
  aktuellerPinsel.aktiv(false);
  bild = null;
  surface.setSize(300, 230);
  cp5.setVisible(true);
}

public void pinselWaehlen(Pinsel p) {
  aktuellerPinsel.aktiv(false);
  aktuellerPinsel = p;
  aktuellerPinsel.aktiv(true);
}

public void textAnzeigen(String text) {
  int textKastenBreite = (int) (bild.pg.width - 2 * randBreite);
  int textKastenHoehe = (int) (height - 4 * randBreite);
  int x = dXBild + (bild.pg.width / 2) - (textKastenBreite / 2);
  int y = (height / 2) - (textKastenHoehe / 2);
  fill(95, 80);
  noStroke();
  rect(x, y, textKastenBreite, textKastenHoehe);
  float textGroesse = (textKastenBreite * textKastenHoehe) / text.length() / 50.0f;
  textGroesse = constrain(textGroesse, 10, 25);
  PFont myFont = createFont("Arial", textGroesse);
  fill(0);
  textFont(myFont);
  text(text, x + randBreite, y + randBreite, textKastenBreite, textKastenHoehe);
}

public void tastaturkuerzelAnzeigen() {
  textAnzeigen(
    "Tastaturk\u00fcrzel\n"
    + "0      --> Radierer\n"
    + "1-9   --> Pinsel ausw\u00e4hlen\n"
    + "o      --> Bild \u00f6ffnen\n"
    + "s      --> Bild speichern\n"
    + "z      --> Undo\n"
    + "l       --> L\u00f6schen\n"
    + "r       --> Reset\n"
    + "m     --> Tastaturk\u00fcrzel anzeigen\n\n"
    + "Anzeige schlie\u00dfen mit beliebiger Taste");
}
  static public void main(String[] passedArgs) {
    String[] appletArgs = new String[] { "mal_o_mat_2_0" };
    if (passedArgs != null) {
      PApplet.main(concat(appletArgs, passedArgs));
    } else {
      PApplet.main(appletArgs);
    }
  }
}
