import processing.core.*; 
import processing.xml.*; 

import java.applet.*; 
import java.awt.Dimension; 
import java.awt.Frame; 
import java.awt.event.MouseEvent; 
import java.awt.event.KeyEvent; 
import java.awt.event.FocusEvent; 
import java.awt.Image; 
import java.io.*; 
import java.net.*; 
import java.text.*; 
import java.util.*; 
import java.util.zip.*; 
import java.util.regex.*; 

public class Ue1_Konrad_markus extends PApplet {

// 1. Auswahl Kreis / Quadrat
// 2. Benutzer den Kreis / das Quatrat ziehen lassen!
// 3. Visualisieren des Erg.

final int APP_STATE_START = 1;
final int APP_STATE_TEST = 2;
final int APP_STATE_RESULTS = 3;
final int APP_STATE_RESULTS_ALL = 4;

final int SHAPE_TYPE_CIRCLE = 1;
final int SHAPE_TYPE_RECT = 2;

final int SCREEN_W = 800;
final int SCREEN_H = 600;

final boolean DEBUG_VIS = false;

int appState;
int shapeType;
PFont fontHeader;

AppStart appStart;
AppTest appTest;
AppResults appResults;

ArrayList results = new ArrayList();  //List with testNum -> Result mapping

public void setup() {
  if (!DEBUG_VIS)
    appState = APP_STATE_START;
  else
    appState = APP_STATE_RESULTS;
    
  shapeType = SHAPE_TYPE_CIRCLE;
    
  smooth();
  size(SCREEN_W, SCREEN_H);
  stroke(255);
  background(0, 0, 0);
  
  fontHeader = loadFont("SansSerif-28.vlw");
  
  appStart = new AppStart();
  appTest = new AppTest();
  appResults = new AppResults();
  
  // fill the results with empty values
  for (int i = 0; i < appTest.testMax; i++) {
    Result res = new Result();
    res.testNum = i;
    
    if (DEBUG_VIS) {  // fill with random values for debugging
      res.userDeviation = random(0.7f, 1.4f);
      res.userLinDeviation = random(-2.0f, 2.0f);
    }
    
    results.add(res);
  }
} 

public void draw() {
  background(0, 0, 0);
  showHeader();
  updateMain();
  showMain();
  showFooter();
}

public void showHeader() {
  fill(255);
  String title = "";
  
  switch (appState) {
    case APP_STATE_START:
      title = "W\u00e4hle eine Form";
    break;
    case APP_STATE_TEST:
      title = "Testphase";
    break;
    case APP_STATE_RESULTS:
      title = "Individuelle Resultate";
    break;
    case APP_STATE_RESULTS_ALL:
      title = "Resulte vergangener Kandidaten";
    break;    
  }
  
  textFont(fontHeader, 28);
  textAlign(LEFT);
  text(title, 20, 50);
}

public void updateMain() {
  switch (appState) {
    case APP_STATE_START:
      appStart.update();
    break;
    case APP_STATE_TEST:
      appTest.update();
    break;
    case APP_STATE_RESULTS:
    case APP_STATE_RESULTS_ALL:
      appResults.update();
    break;
    
  }
}

public void showMain() {
  switch (appState) {
    case APP_STATE_START:
      appStart.display();
    break;
    case APP_STATE_TEST:
      appTest.display();
    break;
    case APP_STATE_RESULTS:
    case APP_STATE_RESULTS_ALL:
      appResults.display();
    break;
    
  }
}

public void showFooter() {
  
}
class AppResults {
  boolean firstCall = true;

  float avgValue = 0.0f;
  float avgDeviation = 0.0f;
  float avgLinDeviation = 0.0f;

  float[] deviationData = null;
  float deviationDataMin = Float.MAX_VALUE;
  float deviationDataMax = Float.MIN_VALUE;

  float[] linDeviationData = null;
  float linDeviationDataMin = Float.MAX_VALUE;
  float linDeviationDataMax = Float.MIN_VALUE;

  PFont fontHeaderText;
  PFont fontVisValues;

  TextButton prevButton;

  AppResults() {
    fontHeaderText = loadFont("SansSerif-20.vlw");
    fontVisValues = loadFont("SansSerif-9.vlw");

    prevButton = new TextButton(370, 550, 60, 20, "OK", color(180), color(220), true);
  }

  public void display() {
    //    fill(200);
    //    textAlign(LEFT);
    //    textFont(fontHeaderText, 20);
    //    text("Average log. deviation x: " + avgDeviation, 20, 80);

    textFont(fontHeaderText, 20);
    textAlign(LEFT);
    fill(200);
    text("Logarithmische Abweichung x = log A' / log A", 50, 100);

    displayVisualizationData(deviationData, 50, 300, 600, 300, max(abs(deviationDataMax), abs(deviationDataMin)), 1.0f);
    displayMinAvgMax(680, 305, deviationDataMin, avgValue, avgDeviation, deviationDataMax);

    prevButton.display();

    /*    textFont(fontHeaderText, 20);
     textAlign(LEFT);
     fill(200);
     text("Lineare Abweichung:", 50, 375);
     
     displayVisualizationData(linDeviationData, 50, 450, 600, 100, 2.0f * max(abs(linDeviationDataMax), abs(linDeviationDataMin)), 1.0f);
     displayMinAvgMax(680, 455, linDeviationDataMin, avgLinDeviation, linDeviationDataMax);*/
  }

  public void displayVisualizationData(float[] data, int xOffset, int yOffset, int visWidth, int visHeight, float dataMaximum, float dataShift) {
    if (data == null) return;

    stroke(100);

    // init values
    int barBorder = 5;
    int barWidth = visWidth / data.length - 2 * barBorder;
    float barHeightScale = 0.9f * visHeight / dataMaximum;
    float scaleSteps = (int)(8.0f / ceil(dataMaximum) * 100.0f) / 100.0f;
    if (scaleSteps < 0.25f) scaleSteps = 0.1f;
    else if (scaleSteps < 0.5f) scaleSteps = 0.25f;
    else if (scaleSteps < 1.0f) scaleSteps = 0.5f;
    else if (scaleSteps > 4.0f) scaleSteps = 4.0f;
    println("dataMaximum: " + dataMaximum);
    println("scaleSteps: " + scaleSteps);

    // make rect around whole diagram
    color(255);
    fill(50);
    rectMode(CORNER);
    stroke(180);
    rect(xOffset, yOffset - visHeight * 0.5f, visWidth, visHeight);

    // display standard deviation area
    int yAvg = yOffset - (int)((avgValue - 1) * barHeightScale);
    noStroke();
    int yStd = (int)(avgDeviation * barHeightScale);
    rectMode(CORNER);
    fill(80);
    rect(xOffset + 1, yOffset - yStd, visWidth - 1, yStd * 2);

    // display avg line
    stroke(200, 200, 0);
    line(xOffset, yAvg, xOffset + visWidth, yAvg);

    // make diagram lines and values
    stroke(180);
    int lineMax = (int)((float)ceil(dataMaximum / 2) * scaleSteps);
    //    if (lineMax % 2 == 0) lineMax++;
    //    if (lineMax > 5) lineMax = 5; 
    //    int maxVal = (lineMax - 1) / 2;
    println("lineMax: " + lineMax);
    //    println("maxVal: " + maxVal);
    for (float i = -lineMax; i <= lineMax; i++) {
      float v = (float)(i/scaleSteps);
      int y = yOffset + (int)(v * barHeightScale);
      if (y <= yOffset - visHeight * 0.5f || y>= yOffset + visHeight * 0.5f) continue;
      line(xOffset, y, xOffset + visWidth, y);

      fill(200);
      textFont(fontVisValues, 9);
      textAlign(RIGHT);
      text(formatValue2(-v + dataShift), xOffset - 5, y + 5);
    }

    fill(200);
    textFont(fontVisValues, 9);
    textAlign(LEFT);
    text("Versuch", xOffset + visWidth + 10, yOffset - visHeight/2 - 5);

    fill(200);
    textFont(fontVisValues, 9);
    textAlign(LEFT);
    text("Versuchserg. x", xOffset + visWidth + 10, yOffset + visHeight/2 + 17);

    // display data
    int xBarOffset = 0;
    float localMax = max((deviationDataMax - dataShift), (dataShift - deviationDataMin));    
    for (int i = 0; i < data.length; i++) {
      float v = data[i];

      // display bar
      float c;

      if (v >= 0.0f) 
        c = v / localMax;
      else
        c = -v / localMax;

      fill(200 * c, 200 * (1-c), 0);  

      rectMode(CORNER);
      rect(xOffset + xBarOffset + barBorder, yOffset, barWidth, -v * barHeightScale);

      // display test number and value
      fill(200);
      textFont(fontVisValues, 9);
      textAlign(CENTER);
      text((i+1), xOffset + xBarOffset + barBorder + barWidth / 2, yOffset - visHeight/2 - 5);
      text(formatValue2(v + dataShift), xOffset + xBarOffset + barBorder + barWidth / 2, yOffset + visHeight/2 + 17);

      xBarOffset += barWidth + 2 * barBorder;
    }
  }

  public void displayMinAvgMax(int xPos, int yPos, float min, float avg, float dev, float max) {
    fill(200);    
    textFont(fontHeaderText, 20);
    textAlign(LEFT);
    fill(200, 0, 0);
    text("Max.: " + roundByValue(max, 100.0f), xPos, yPos - 30);
    fill(200, 200, 0);
    text("Mittel: " + roundByValue(avg, 100.0f), xPos, yPos);
    fill(80);
    text("Sigma: " + roundByValue(dev, 100.0f), xPos, yPos + 30);
    fill(200, 0, 0);
    text("Min.: " + roundByValue(min, 100.0f), xPos, yPos + 60);
  }

  public void update() {
    if (firstCall) {
      buildVisualizationData();
      firstCall = false;
    }

    prevButton.update();

    if (prevButton.pressed()) {
      appState = APP_STATE_START;
    }
  }

  public void buildVisualizationData() {
    float deviationSum = 0.0f;
    float avgSum = 0.0f;
    float linDeviationSum = 0.0f;
    deviationData = new float[results.size()];
    linDeviationData = new float[results.size()];

    for (int i = 0; i < results.size(); i++) {
      Result res = (Result)results.get(i);
      deviationData[i] = res.userDeviation - 1;
      linDeviationData[i] = res.userLinDeviation;
      deviationSum += (res.userDeviation - 1) * (res.userDeviation - 1);
      avgSum += res.userDeviation;
      linDeviationSum += res.userLinDeviation;

      if (res.userDeviation > deviationDataMax) {
        deviationDataMax = res.userDeviation;
      }

      if (res.userDeviation < deviationDataMin) {
        deviationDataMin = res.userDeviation;
      }      


      if (res.userLinDeviation > linDeviationDataMax) {
        linDeviationDataMax = res.userLinDeviation;
      }

      if (res.userLinDeviation < linDeviationDataMin) {
        linDeviationDataMin = res.userLinDeviation;
      }
    }

    avgValue = avgSum / results.size();
    avgDeviation = sqrt(deviationSum / results.size());
    avgLinDeviation = linDeviationSum / results.size();
  }

  public float roundByValue(float v, float r) {
    return round(v * r) / r;
  }

  public String formatValue1(float v) {
    DecimalFormat df =   new DecimalFormat("0.0");

    return df.format(v);
  }

  public String formatValue2(float v) {
    DecimalFormat df =   new DecimalFormat("0.0#");

    return df.format(v);
  }
}

class AppStart {
  CircleButton circleButton;
  RectButton rectButton;
  
  AppStart() {
    int size = 100;
    circleButton = new CircleButton(SCREEN_W / 2 - size, SCREEN_H / 2, size, color(180, 0, 0), color(220, 0, 0), true);
    rectButton = new RectButton(SCREEN_W / 2 + size, SCREEN_H / 2, size, color(180, 0, 0), color(220, 0, 0), true);
  }
  
  public void display() {
    circleButton.display();
    rectButton.display();
  }
  
  public void update() {
     circleButton.update();
     rectButton.update();
     
     if (circleButton.pressed()) {
       appState = APP_STATE_TEST;
       shapeType = SHAPE_TYPE_CIRCLE;
     }
     
     if (rectButton.pressed()) {
       appState = APP_STATE_TEST;
       shapeType = SHAPE_TYPE_RECT;
     }
  }
}

class AppTest {
  int testNum;
  int testMax;
  int testType;   // first five tests: (testType = 0) create new shapes, last five tests: (testType = 1) select right ratio

  PFont fontHeaderText;

  float[] shapeTestSizes = {
    0.25f, 0.33f, 0.5f, 0.75f, 1.5f, 2.0f, 2.5f, 3.0f, 4.0f, 5.0f
  };

  //  float[] shapeTestSizesShuffled;

  int baseShapeSize;          // base width of the original shape
  float baseShapeArea;        // base area of the original shape  
  float origShapeAreaRatio;   // the ratio by which baseShapeArea is increased
  float origShapeArea;        // is baseShapeArea * origShapeSizeRatio
  int origShapeSize;          // the width of the original shape with the area of origShapeArea
  float expectedAreaRatio;    // the area ratio the user shall achieve
  float userShapeArea;        // is calculated from the user's input (testType = 0) or baseShapeArea * expectedAreaRatio (testType = 1)
  int userShapeSize;          // the width of the user's shape

  int selectedRatioIndex = -1;

  int expAreaSize = 300;
  int leftAreaCenterX = SCREEN_W / 2 - (expAreaSize / 2 + 10);
  int rightAreaCenterX = SCREEN_W / 2 + (expAreaSize / 2 + 10);
  int areaCenterY = SCREEN_H / 2 - 20;

  RectButton areaLeft;
  RectButton areaRight;

  //TriangleButton prevButton;
  TriangleButton nextButton;

  ArrayList answerSelection;  // List of TextButtons

  AppTest() {
    testNum = 0;
    testMax = 20;

    fontHeaderText = loadFont("SansSerif-20.vlw");

    //    shapeTestSizesShuffled = shuffle(shapeTestSizes);

    baseShapeSize = 50;
    baseShapeArea = calcArea(baseShapeSize);
    userShapeSize = 0;

    areaLeft = new RectButton(leftAreaCenterX, areaCenterY, expAreaSize, color(40), color(40), false);
    areaRight = new RectButton(rightAreaCenterX, areaCenterY, expAreaSize, color(40), color(40), false);

    //prevButton = new TriangleButton(110, 550, 20, 30, 90, color(180), color(220), true);
    nextButton = new TriangleButton(690, 550, 20, 30, 270, color(180), color(220), true);

    answerSelection = new ArrayList();
    for (int i = 0; i < shapeTestSizes.length; i++) {
      String label = generateStringFromRatio(shapeTestSizes[i]);
      int col = i % (shapeTestSizes.length / 2);
      int row = (i / (shapeTestSizes.length / 2));
      TextButton btn = new TextButton(150 + col * 125, 500 + row * 35, 120, 30, label, color(180), color(220), true);
      answerSelection.add(btn);
    }

    initValues();
  }

  //--------------------------------------------//
  // main display functions //
  //--------------------------------------------//

  public void display() {
    fill(200);
    textAlign(LEFT);
    textFont(fontHeaderText, 20);
    text("Test #" + (testNum + 1) + "/" + testMax + ":", 20, 80);

    displayShapeTest();
    displayFooter();
  }

  public void displayFooter() {
    if (testNum > 0) {
      //prevButton.display();
    }

    if (testType == 0 && userShapeSize > 0) {
      nextButton.display();
    }
  }

  //--------------------------------------------//
  // update functions //
  //--------------------------------------------//

  public void update() {
    if (testNum >= testMax) {
      appState = APP_STATE_RESULTS;
      testNum = 0;
      initValues();
      return;
    }

    updateShapeTest();
    updateFooter();
  }

  public void updateFooter() {
    //    if (testNum > 0) {
    //      prevButton.update();
    //
    //      if (prevButton.pressed()) {
    //        saveResults();
    //        testNum--;
    //      }
    //    }

    if (testType == 0 && userShapeSize > 0) {
      nextButton.update();

      if (nextButton.pressed()) {
        saveResults();
        testNum++;
      }
    }
  }

  //--------------------------------------------//
  // result handling //
  //--------------------------------------------//

  public void saveResults() {
    // update Result object
    Result res = (Result)results.get(testNum);
    res.expectedRatio = expectedAreaRatio;
    res.testType = testType;
    res.origShapeArea = origShapeArea;

    if (testType == 0) {
      res.userShapeArea = calcArea(userShapeSize);
      res.userRatio = res.userShapeArea / res.origShapeArea;
    } 
    else {
      res.userShapeArea = res.origShapeArea * shapeTestSizes[selectedRatioIndex];
      res.userRatio = shapeTestSizes[selectedRatioIndex];
    }

    res.userDeviation = logAB(res.origShapeArea * res.expectedRatio, res.userShapeArea);
    
    float expectedArea = res.origShapeArea * res.expectedRatio;
    if (expectedArea <= res.userShapeArea) {
      res.userLinDeviation = res.userShapeArea / expectedArea;
    }
    else {
      res.userLinDeviation = -expectedArea / res.userShapeArea;
    }

    // reset results
    initValues();

    //println("Saved result:" + res);
  }

  //--------------------------------------------//
  // shape test functions //
  //--------------------------------------------//

  public void initValues() {
    selectedRatioIndex = -1;
    userShapeSize = 0;

    // set the test tyoe
    testType = (testNum+1) / shapeTestSizes.length;  // 0 is creating a shape, 1 is selecting the shape's area ratio

    // calculate values for the "left side", i. e. for the reference shape
    int testIndex = (int)random(shapeTestSizes.length);
    origShapeAreaRatio = shapeTestSizes[testIndex];
    origShapeArea = baseShapeArea * origShapeAreaRatio;
    origShapeSize = (int)calcWidthFromArea(origShapeArea);

    // calculate values for the "right side", i. e. for the user's shape
    int minIndex = 0;
    int maxIndex = shapeTestSizes.length;
    
    // ensure it doesnt get too big or too small
    if (testIndex < 3)
      minIndex = 3;
    else if (testIndex > 6)
      maxIndex = 6;  
    
    expectedAreaRatio = shapeTestSizes[(int)random(minIndex, maxIndex)];

    if (testType == 1) {
      userShapeArea = origShapeArea * expectedAreaRatio;
      userShapeSize = (int)calcWidthFromArea(userShapeArea);
    }
  }

  public void displayShapeTest() {
    // help text
    fill(200);
    textFont(fontHeaderText, 20);
    textAlign(CENTER);

    if (testType == 0) {
      text("Mache die Form auf der rechten Seite " + generateStringFromRatio(expectedAreaRatio) + " wie die auf der linken.", 400, 110);
    } 
    else {
      text("W\u00e4hle die richtige Antwort f\u00fcr die rechte Seite aus.", 400, 110);
      text("Die Form auf der rechten Seite ist...", 400, 460);
    }

    // show "experiment area"
    areaLeft.display();
    areaRight.display();

    if (testType == 1) {
      for (int i = 0; i < answerSelection.size(); i++) {
        TextButton btn = (TextButton)answerSelection.get(i);
        btn.display();
      }
    }

    // show original shape on the left
    fill(180, 0, 0);
    if (shapeType == SHAPE_TYPE_CIRCLE) {
      ellipse(leftAreaCenterX, areaCenterY, origShapeSize, origShapeSize);
    } 
    else {
      rect(leftAreaCenterX, areaCenterY, origShapeSize, origShapeSize);
    }

    // show user's shape on the right
    fill(180, 0, 0);
    if (shapeType == SHAPE_TYPE_CIRCLE) {
      ellipse(rightAreaCenterX, areaCenterY, userShapeSize, userShapeSize);
    } 
    else {
      rect(rightAreaCenterX, areaCenterY, userShapeSize, userShapeSize);
    }
  }

  public void updateShapeTest() {
    // manage user interaction
    if (testType == 0) {
      areaRight.update();

      if (areaRight.pressed()) {
        userShapeSize = (int)dist(mouseX, mouseY, rightAreaCenterX, areaCenterY) * 2;
        if (userShapeSize > expAreaSize - 5) {
          userShapeSize = expAreaSize - 5;
        }
      }
    } 
    else {
      for (int i = 0; i < answerSelection.size(); i++) {
        TextButton btn = (TextButton)answerSelection.get(i);
        btn.update();

        if (btn.pressed()) {
          selectedRatioIndex = i;
          saveResults();
          testNum++;

          return;
        }
      }
    }
  }

  //--------------------------------------------//
  // helper //
  //--------------------------------------------//

  public float calcArea(float width) {
    if (shapeType == SHAPE_TYPE_RECT) {
      return width * width;
    } 
    else {
      return PI / 4.0f * width * width;
    }
  }

  public float calcWidthFromArea(float area) {
    if (shapeType == SHAPE_TYPE_RECT) {
      return sqrt(area);
    } 
    else {
      return sqrt(area / PI) * 2.0f;
    }
  }

  public String generateStringFromRatio(float ratio) {
    String s;
    s = ratio + " mal gr\u00f6\u00dfer";

    if (ratio == 0.25f) {
      s = "4 mal kleiner";
    } 

    if (ratio == 0.33f) {
      s = "3 mal kleiner";
    } 

    if (ratio == 0.5f) {
      s = "halb so gro\u00df";
    } 

    if (ratio == 0.75f) {
      s = "3/4 so gro\u00df";
    }

    return s;
  }
  
  public float logAB (float a, float b) {
    return log(b) / log(a);
  }

  //  // Code by "fjen", taken from http://processing.org/discourse/yabb2/YaBB.pl?num=1194572947
  //  float[] shuffle(float[] toShuffle) {
  //    float[] shuffled = new float[toShuffle.length];
  //
  //    int i=0;
  //
  //    while ( toShuffle.length > 0 )
  //    {
  //      int rnd = int(random(toShuffle.length));  // by default random() never returns the given max value
  //      shuffled[i] = toShuffle[rnd];
  //      i++;
  //      if ( rnd > 0 && rnd < toShuffle.length-1 )
  //        toShuffle = concat(subset(toShuffle,0,rnd), subset(toShuffle,rnd+1,toShuffle.length-rnd-1));
  //      else if ( rnd == 0 )
  //        toShuffle = subset(toShuffle,1,toShuffle.length-1);
  //      else
  //        toShuffle = shorten( toShuffle );
  //    }
  //
  //    return shuffled;
  //  }
}

class Button
{
  int x, y;
  int sizeW;
  int sizeH;
  int basecolor, highlightcolor;
  int currentcolor;
  boolean over = false;
  boolean pressed = false;
  int lastClick = 0;
  int clickLockTime = 0;  

  public void update() 
  {
    if(over()) {
      currentcolor = highlightcolor;
    } 
    else {
      currentcolor = basecolor;
    }
  }

  public boolean pressed() 
  {
    if (clickLockTime > 0) {
      boolean pressed = (millis() - lastClick > clickLockTime && mousePressed && over);
      if (pressed)
        lastClick = millis();
      
      return pressed;
    }
    
    return (mousePressed && over);   
  }

  public boolean over() 
  { 
    return true; 
  }

  public boolean overRect(int x, int y, int width, int height) 
  {
    if (mouseX >= x && mouseX <= x+width && 
      mouseY >= y && mouseY <= y+height) {
      return true;
    } 
    else {
      return false;
    }
  }

  public boolean overCircle(int x, int y, int diameter) 
  {
    float disX = x - mouseX;
    float disY = y - mouseY;
    if(sqrt(sq(disX) + sq(disY)) < diameter/2 ) {
      return true;
    } 
    else {
      return false;
    }
  }

}

class CircleButton extends Button
{ 
  CircleButton(int ix, int iy, int isize, int icolor, int ihighlight, boolean clickLock) 
  {
    x = ix;
    y = iy;
    sizeW = isize;
    sizeH = isize;
    basecolor = icolor;
    highlightcolor = ihighlight;
    currentcolor = basecolor;
    
    if (clickLock)
      clickLockTime = 500;
    else 
      clickLockTime = 0;
  }

  public boolean over() 
  {
    if( overCircle(x, y, sizeW) ) {
      over = true;
      return true;
    } 
    else {
      over = false;
      return false;
    }
  }

  public void display() 
  {
    stroke(255);
    fill(currentcolor);
    ellipse(x, y, sizeW, sizeW);
  }
}

class RectButton extends Button
{
  RectButton(int ix, int iy, int isize, int icolor, int ihighlight, boolean clickLock) 
  {
    x = ix;
    y = iy;
    sizeW = isize;
    sizeH = isize;
    basecolor = icolor;
    highlightcolor = ihighlight;
    currentcolor = basecolor;
    
    if (clickLock)
      clickLockTime = 500;
    else 
      clickLockTime = 0;
  }

  public boolean over() 
  {
    if( overRect(x-sizeW/2, y-sizeW/2, sizeW, sizeW) ) {
      over = true;
      return true;
    } 
    else {
      over = false;
      return false;
    }
  }

  public void display() 
  {
    stroke(255);
    fill(currentcolor);
    rectMode(CENTER);
    rect(x, y, sizeW, sizeW);
  }
}

class TriangleButton extends Button
{
  float angle;
  
  TriangleButton(int ix, int iy, int isizeW, int isizeH, float iangle, int icolor, int ihighlight, boolean clickLock) 
  {
    x = ix;
    y = iy;
    sizeW = isizeW;
    sizeH = isizeH;
    angle = radians(iangle);
    basecolor = icolor;
    highlightcolor = ihighlight;
    currentcolor = basecolor;
    
    if (clickLock)
      clickLockTime = 500;
    else 
      clickLockTime = 0;
  }

  public boolean over() 
  {
    if( overRect(x-sizeW/2, y-sizeH/2, sizeW, sizeH) ) {
      over = true;
      return true;
    } 
    else {
      over = false;
      return false;
    }
  }

  public void display() 
  {
    //shapeMode(CENTER);
    pushMatrix();
    stroke(255);
    fill(currentcolor);
    translate(x, y);
    rotate(angle);
    triangle(-sizeW/2, -sizeH/2, sizeW/2.5f, -sizeH/2, 0, sizeH/2);
    popMatrix();
  }
}

class TextButton extends Button
{
  PFont labelFont;
  String label;
  
  TextButton(int ix, int iy, int isizeW, int isizeH, String ilabel, int icolor, int ihighlight, boolean clickLock) 
  {
    x = ix;
    y = iy;
    sizeW = isizeW;
    sizeH = isizeH;
    label = ilabel;
    basecolor = icolor;
    highlightcolor = ihighlight;
    currentcolor = basecolor;
    
    labelFont = loadFont("SansSerif-14.vlw");
    
    if (clickLock)
      clickLockTime = 500;
    else 
      clickLockTime = 0;
  }

  public boolean over() 
  {
    if( overRect(x-sizeW/2, y-sizeH/2, sizeW, sizeH) ) {
      over = true;
      return true;
    } 
    else {
      over = false;
      return false;
    }
  }

  public void display() 
  {
    stroke(255);
    fill(currentcolor);
    rectMode(CENTER);
    rect(x, y, sizeW, sizeH);
    fill(255);
    textFont(labelFont, 14);
    textAlign(CENTER);
    text(label, x, y + 5);
  }
}
class Result {
  int testNum = 0;
  int testType = 0;
  float expectedRatio = 0;
  float origShapeArea = 0;
  float userRatio = 0;
  float userShapeArea = 0;
  float userDeviation = 0;
  float userLinDeviation = 0;
  
  public String toString() {
    return "Result for Test#" + testNum + ":\n" +
      " testType: " + testType + "\n" +
      " expectedRatio: " + expectedRatio + "\n" +
      " origShapeArea: " + origShapeArea + "\n" +
      " userRatio: " + userRatio + "\n" +
      " userShapeArea: " + userShapeArea + "\n" +
      " userDeviation: " + userDeviation + "\n" +
      " userLinDeviation: " + userLinDeviation;
  }
}
  static public void main(String args[]) {
    PApplet.main(new String[] { "--bgcolor=#FFFFFF", "Ue1_Konrad_markus" });
  }
}
