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

import java.awt.*; 
import java.awt.event.*; 
import java.awt.datatransfer.*; 
import javax.swing.*; 
import java.io.*; 
import java.util.Hashtable; 
import java.util.HashSet; 
import java.util.Set; 
import java.util.Vector; 
import java.util.Stack; 
import java.util.List; 
import java.util.regex.*; 

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

// Wall'n'Ball Arena Editor - Thomas Laubach, Mithotronic, 2017
// Many thanks to Michael Ro\u00dfkopf

 // Needed for clipboard functionality












final int red_brick = 0;
final int green_brick = 1;
final int blue_brick = 2;
final int yellow_brick = 3;
final int violet_brick = 4;
final int orange_brick = 5;
final int solid_brick = 6;
final int empty_brick = 7;
final int horiz_wall_low = 8;
final int horiz_wall_high= 9;
final int vert_wall = 10;
final int corner_nw = 11;
final int corner_ne = 12;
final int corner_sw = 13;
final int corner_se = 14;

final int UPPER_BAT = 0;
final int LOWER_BAT = 1;
final int LEFT_BAT = 2;
final int RIGHT_BAT = 3;

final int UPPER_GAP = 0;
final int LOWER_GAP = 1;
final int LEFT_GAP = 2;
final int RIGHT_GAP = 3;

final int RED_BRICK = 0;
final int GREEN_BRICK = 1;
final int BLUE_BRICK = 2;
final int YELLOW_BRICK = 3;
final int VIOLET_BRICK = 4;
final int ORANGE_BRICK = 5;
final int SOLID_BRICK = 6;
final int EMPTY_BRICK = 7;
final int HORIZ_WALL_LOW = 8;
final int HORIZ_WALL_HIGH = 9;
final int VERT_WALL = 10;
final int CORNER_NW = 11;
final int CORNER_NE = 12;
final int CORNER_SW = 13;
final int CORNER_SE = 14;

final int ledOffIndex = 4864;

boolean upperBorder = true;
boolean lowerBorder = true;
boolean leftBorder = true;
boolean rightBorder = true;

boolean upperBatActive = false;
boolean lowerBatActive = true;
boolean leftBatActive = false;
boolean rightBatActive = false;

boolean leftLEDMatrixPanel = false;

int selectedBrick; // Selected brick

ToolGroup toolGroup = new ToolGroup();
ElementSelector redBrick, greenBrick, blueBrick, violetBrick, yellowBrick, orangeBrick, solidBrick, emptyBrick;
ElementSelector horizWallHigh, horizWallLow, vertWall, cornerNW, cornerNE, cornerSW, cornerSE;

ExportButton exportButton;
ClearButton clearButton;
LoadButton loadButton;

BatSelectButton upperBatActiveSel, lowerBatActiveSel, leftBatActiveSel, rightBatActiveSel;
GapSelectButton upperGap, lowerGap, leftGap, rightGap;

public int[] indexColor = new int[4096+768+64+1];
public PFont f;
public int currentLED_x;
public int currentLED_y;
public int formerLED_x;
public int formerLED_y;
public int[][] frameBuffer = new int[32][32]; // Contain the colors indices placed into the LEDmePlay framebuffer
public int foregroundCol;
public int backgroundCol;
public int contourCol;
public int batColor;
public int black;
public int white;
public int grey, darkGrey;
public int red, green, blue, violet, yellow, orange, solid, empty;
public boolean useColor1 = true;
public boolean useColor2 = false;
public int dotDiameter = 13;
public int indexColor1 = 255;
public int indexColor2 = ledOffIndex;
public boolean diskOperationPending = false;
public boolean dragging;
public boolean fileLoaded = false;

public void setup() 
{
  
  colorMode(RGB, 16, 16, 16); // Turn on RGB color mode 4/4/4 (2^4 = 16)
  background(6);
  
  frameRate(50);

  red = color(13, 2, 2);
  green = color(2, 13, 2);
  blue = color(2, 2, 13);
  violet = color(13, 2, 13);
  yellow = color(13, 13, 2);
  orange = color(13, 7, 2);
  solid = color(10, 10, 13);
  white = color(15, 15, 15);
  black = color(0, 0, 0);
  grey = color(9, 9, 9);
  darkGrey = color(6, 6, 6);
  contourCol = color(0, 0, 0);
  empty = color(0, 0, 0);
  batColor = color(16, 8, 8);

  clearLEDmePlayFrameBuffer();
  drawEmptyMatrix();

  foregroundCol = indexColor[indexColor1];
  backgroundCol = indexColor[indexColor2];

  redBrick = new ElementSelector(RED_BRICK, 940, 40, 75, 50);
  redBrick.setToolGroup(toolGroup);
  greenBrick = new ElementSelector(GREEN_BRICK, 940, 100, 75, 50);
  greenBrick.setToolGroup(toolGroup);
  violetBrick = new ElementSelector(VIOLET_BRICK, 940, 160, 75, 50); 
  violetBrick.setToolGroup(toolGroup);
  blueBrick = new ElementSelector(BLUE_BRICK, 940, 220, 75, 50); 
  blueBrick.setToolGroup(toolGroup);
  yellowBrick = new ElementSelector(YELLOW_BRICK, 940, 280, 75, 50); 
  yellowBrick.setToolGroup(toolGroup);
  orangeBrick = new ElementSelector(ORANGE_BRICK, 940, 340, 75, 50); 
  orangeBrick.setToolGroup(toolGroup);
  emptyBrick = new ElementSelector(EMPTY_BRICK, 940, 460, 75, 50); 
  emptyBrick.setToolGroup(toolGroup);
  horizWallHigh = new ElementSelector(HORIZ_WALL_HIGH, 1040, 40, 75, 50);
  horizWallHigh.setToolGroup(toolGroup);
  horizWallLow = new ElementSelector(HORIZ_WALL_LOW, 1040, 100, 75, 50);
  horizWallLow.setToolGroup(toolGroup);
  vertWall = new ElementSelector(VERT_WALL, 1040, 160, 75, 50);
  vertWall.setToolGroup(toolGroup);
  cornerNW = new ElementSelector(CORNER_NW, 1040, 220, 75, 50);
  cornerNW.setToolGroup(toolGroup);
  cornerNE = new ElementSelector(CORNER_NE, 1040, 280, 75, 50);
  cornerNE.setToolGroup(toolGroup);
  cornerSW = new ElementSelector(CORNER_SW, 1040, 340, 75, 50);
  cornerSW.setToolGroup(toolGroup);
  cornerSE = new ElementSelector(CORNER_SE, 1040, 400, 75, 50);
  cornerSE.setToolGroup(toolGroup);
  solidBrick = new ElementSelector(SOLID_BRICK, 1040, 460, 75, 50); 
  solidBrick.setToolGroup(toolGroup);

  upperBatActiveSel = new BatSelectButton(UPPER_BAT, 1140, 40, 200, 50, upperBatActive);
  lowerBatActiveSel = new BatSelectButton(LOWER_BAT, 1140, 100, 200, 50, lowerBatActive);
  leftBatActiveSel = new BatSelectButton(LEFT_BAT, 1140, 160, 200, 50, leftBatActive);
  rightBatActiveSel = new BatSelectButton(RIGHT_BAT, 1140, 220, 200, 50, rightBatActive);
  drawBats();

  upperGap = new GapSelectButton(UPPER_GAP, 1140, 280, 200, 50, upperBorder);
  lowerGap = new GapSelectButton(LOWER_GAP, 1140, 340, 200, 50, lowerBorder);
  leftGap = new GapSelectButton(LEFT_GAP, 1140, 400, 200, 50, leftBorder);
  rightGap = new GapSelectButton(RIGHT_GAP, 1140, 460, 200, 50, rightBorder);
  drawArenaBorder();

  clearButton = new ClearButton(940, 560, 175, 50);
  exportButton = new ExportButton(940, 620, 105, 50);
  loadButton = new LoadButton(1055, 620, 85, 50);

  f = loadFont("ArialMT-30.vlw");
  if (f == null) 
  { 
    f = loadFont("ArialMT-30.vlw");
  }
  textFont(f, 30);

  formerLED_x = 32;
  formerLED_y = 32;
  currentLED_x = 32;
  currentLED_y = 32;
}

public void draw() 
{  
  drawFrameBuffer();
  drawBats();

  if (diskOperationPending)
  {
    // Inform user that we are busy
    fill(0);
    textFont(f, 30);
    fill(6, 6, 6);
    noStroke();
    rect(39, 920, 900, 40);
    fill(black);
    text("Please wait - disk operation pending...", 40, 950);

    return;
  }

  // Mouse is over the LED matrix panel
  if ((mouseX > 36) && (mouseX < 865) && (mouseY > 36) && (mouseY < 845))
  {
    leftLEDMatrixPanel = false;

    /*
    // Restore the full row of bricks at old mouse position
     for (int i = 1; i < 29; i = i + 3)
     {
     if ((formerLED_y+1) % 2 == 0)
     {
     restoreBrick(i, formerLED_y-2);
     restoreBrick(i, formerLED_y);
     restoreBrick(i, formerLED_y+2);
     }
     }
     */

    currentLED_x = (mouseX - 40) / 27;
    currentLED_y = (mouseY - 40) / 27;

    formerLED_x = currentLED_x;
    formerLED_y = currentLED_y;

    if (  (((currentLED_x+2) % 3 == 0) && ((currentLED_y+1) % 2 == 0)) ||
      (((currentLED_x+2) % 3 == 1) && ((currentLED_y+1) % 2 == 0)) ||
      (((currentLED_x+2) % 3 == 2) && ((currentLED_y+1) % 2 == 0)) ||
      (((currentLED_x+2) % 3 == 0) && ((currentLED_y+1) % 2 == 1)) ||
      (((currentLED_x+2) % 3 == 1) && ((currentLED_y+1) % 2 == 1)) ||
      (((currentLED_x+2) % 3 == 2) && ((currentLED_y+1) % 2 == 1))
      )
    {
      int grid_x = 1+((currentLED_x-1)/3)*3;
      int grid_y = 1+((currentLED_y-1)/2)*2;
      drawMouseCursor(grid_x, grid_y);
    }
  } 
  else // Mouse has left the LED matrix panel area
  {
    if (!leftLEDMatrixPanel) 
    { 
      drawFrameBuffer(); 
      leftLEDMatrixPanel = true;
    }
  }

  if (fileLoaded == true)
  { 
    fileLoaded = false; 

    drawEmptyMatrix();
    drawFrameBuffer();
    drawArenaBorder();
 
    leftBatActiveSel.repaint();
    rightBatActiveSel.repaint();
    upperBatActiveSel.repaint();
    lowerBatActiveSel.repaint();
    
    fill(6, 6, 6);
    noStroke();
    rect(39, 920, 900, 40);
  }
}


public void drawFrameBuffer()
{
  noStroke();
  for (int i = 1; i < 32; i = i + 3)
  {
    for (int j = 1; j < 32; j = j + 2)
    {
      restoreBrick(i, j);
    }
  }
}


public void drawBats()
{
  int x, y;
  if (upperBatActive)
  {
    fill(batColor);
    y = 1;
    for (x = 13; x < 17; x++)
    {
      ellipse(50 + (x+0) * 27, 50 + (y+0) * 27, dotDiameter, dotDiameter);
    }
  }
  /*
  else
  {
    fill(empty);
    y = 1;
    for (x = 13; x < 17; x++)
    {
      ellipse(50 + (x+0) * 27, 50 + (y+0) * 27, dotDiameter, dotDiameter);
    }
  }
*/

  if (lowerBatActive)
  {
    fill(batColor);
    y = 30;
    for (x = 13; x < 17; x++)
    {
      ellipse(50 + (x+0) * 27, 50 + (y+0) * 27, dotDiameter, dotDiameter);
    }
  } 

  if (leftBatActive)
  {
    fill(batColor);
    x = 1;
    for (y = 13; y < 17; y++)
    {
      ellipse(50 + (x+0) * 27, 50 + (y+0) * 27, dotDiameter, dotDiameter);
    }
  } 

  if (rightBatActive)
  {
    fill(batColor);
    x = 30;
    for (y = 13; y < 17; y++)
    {
      ellipse(50 + (x+0) * 27, 50 + (y+0) * 27, dotDiameter, dotDiameter);
    }
  } 
}

public void drawArenaBorder()
{
  int x, y;

  if (upperBorder) 
  {
    fill(solid);
    y = 0;
    for (x = 0; x < 32; x++)
    {
      ellipse(50 + (x+0) * 27, 50 + (y+0) * 27, dotDiameter, dotDiameter);
      frameBuffer[x][y] = solid_brick;
    }
  } else
  {
    fill(empty);
    y = 0;
    for (x = 1; x < 32; x++)
    {
      ellipse(50 + (x+0) * 27, 50 + (y+0) * 27, dotDiameter, dotDiameter);
      frameBuffer[x][y] = empty_brick;
    }
  }

  if (lowerBorder) 
  {
    fill(solid);
    y = 31;
    for (x = 0; x < 32; x++)
    {
      ellipse(50 + (x+0) * 27, 50 + (y+0) * 27, dotDiameter, dotDiameter);
      frameBuffer[x][y] = solid_brick;
    }
  } else
  {
    fill(empty);
    y = 31;
    for (x = 0; x < 32; x++)
    {
      ellipse(50 + (x+0) * 27, 50 + (y+0) * 27, dotDiameter, dotDiameter);
      frameBuffer[x][y] = empty_brick;
    }
  }

  if (leftBorder) 
  {
    fill(solid);
    x = 0;
    for (y = 0; y < 32; y++)
    {
      ellipse(50 + (x+0) * 27, 50 + (y+0) * 27, dotDiameter, dotDiameter);
      frameBuffer[x][y] = solid_brick;
    }
  } else
  {
    fill(empty);
    x = 0;
    for (y = 0; y < 32; y++)
    {
      ellipse(50 + (x+0) * 27, 50 + (y+0) * 27, dotDiameter, dotDiameter);
      frameBuffer[x][y] = empty_brick;
    }
  }

  if (rightBorder) 
  {
    fill(solid);
    x = 31;
    for (y = 0; y < 32; y++)
    {
      ellipse(50 + (x+0) * 27, 50 + (y+0) * 27, dotDiameter, dotDiameter);
      frameBuffer[x][y] = solid_brick;
    }
  } else
  {
    fill(empty);
    x = 31;
    for (y = 0; y < 32; y++)
    {
      ellipse(50 + (x+0) * 27, 50 + (y+0) * 27, dotDiameter, dotDiameter);
      frameBuffer[x][y] = empty_brick;
    }
  }
}

// Draws a simplified representation of the front of the LED matrix panel
public void drawEmptyMatrix()
{
  noStroke();
  strokeWeight(1);
  fill(1);
  rect(27, 27, 32*27+20, 32*27+20);
  fill(indexColor[ledOffIndex]);
  for (int x = 0; x < 32*27; x = x + 27  )
  {
    for (int y = 0; y < 32*27; y = y + 27)
    {
      ellipse(50+x, 50+y, dotDiameter, dotDiameter);
    }
  }
}

// Clears the LEDmePlay's frame buffer, also empties the backup buffer
public void clearLEDmePlayFrameBuffer()
{
  for (int x=0; x < 32; x++)
  {
    for (int y = 0; y < 32; y++)
    {
      frameBuffer[x][y] = empty_brick;
    }
  }
}

public void restoreLED(int x, int y)
{
  if ((x < 1) || (y < 1) || (x > 30) || (y > 30)) { 
    return;
  }

  int c1 = 0, c2 = 0, c3 = 0, c4 = 0, c5 = 0, c6 = 0;
  int selectedBrick = frameBuffer[x][y]; 
  switch(selectedBrick)
  {
  case red_brick:    
    c1 = color(13, 2, 2); 
    c2 = color(12, 2, 2); 
    c3 = color(11, 2, 2); 
    c4 = color(10, 2, 2); 
    c5 = color(9, 2, 2); 
    c6 = color(8, 2, 2); 
    break;
  case green_brick:  
    c1 = color(2, 13, 2); 
    c2 = color(2, 12, 2); 
    c3 = color(2, 11, 2); 
    c4 = color(2, 10, 2); 
    c5 = color(2, 9, 2); 
    c6 = color(2, 8, 2); 
    break;
  case blue_brick:   
    c1 = color(2, 2, 13); 
    c2 = color(2, 2, 12); 
    c3 = color(2, 2, 11); 
    c4 = color(2, 2, 10); 
    c5 = color(2, 2, 9); 
    c6 = color(2, 2, 8); 
    break;
  case yellow_brick: 
    c1 = color(13, 13, 2); 
    c2 = color(12, 12, 2); 
    c3 = color(11, 11, 2); 
    c4 = color(10, 10, 2); 
    c5 = color(9, 9, 2); 
    c6 = color(8, 8, 2); 
    break;
  case violet_brick:  
    c1 = color(13, 2, 13); 
    c2 = color(12, 2, 12); 
    c3 = color(11, 2, 11); 
    c4 = color(10, 2, 10); 
    c5 = color(9, 2, 9); 
    c6 = color(8, 2, 8); 
    break;
  case orange_brick:  
    c1 = color(13, 7, 2); 
    c2 = color(12, 7, 2); 
    c3 = color(11, 7, 2); 
    c4 = color(10, 7, 2); 
    c5 = color(9, 7, 2); 
    c6 = color(8, 7, 2); 
    break;
  case solid_brick:
    c1 = solid;
    c2 = solid;
    c3 = solid;
    c4 = solid;
    c5 = solid;
    c6 = solid;
    break;
  } 

  if (  (selectedBrick == red_brick) || (selectedBrick == green_brick) || (selectedBrick == blue_brick) ||  (selectedBrick == yellow_brick) ||
    (selectedBrick == orange_brick) || (selectedBrick == violet_brick) || (selectedBrick == empty_brick) || (selectedBrick == solid_brick) )
  {
    if      ( ((x+2) % 3 == 0) && ((y+1) % 2 == 0) ) { 
      fill(c1);
    } else if ( ((x+2) % 3 == 1) && ((y+1) % 2 == 0) ) { 
      fill(c2);
    } else if ( ((x+3) % 3 == 2) && ((y+1) % 2 == 0) ) { 
      fill(c3);
    } else if ( ((x+3) % 3 == 0) && ((y+1) % 2 == 1) ) { 
      fill(c4);
    } else if ( ((x+3) % 3 == 1) && ((y+1) % 2 == 1) ) { 
      fill(c5);
    } else if ( ((x+3) % 3 == 2) && ((y+1) % 2 == 1) ) { 
      fill(c6);
    }
    ellipse(50 + x * 27, 50 + y * 27, dotDiameter, dotDiameter);
  }
}

public void restoreBrick(int x, int y)
{
  if ((x < 1) || (y < 1) || (x > 30) || (y > 30)) { 
    return;
  }

  int selectedBrick = frameBuffer[x][y];

  if  ( (selectedBrick == red_brick) || (selectedBrick == green_brick) || (selectedBrick == blue_brick) ||  (selectedBrick == yellow_brick) ||
    (selectedBrick == orange_brick) || (selectedBrick == violet_brick) || (selectedBrick == empty_brick) || (selectedBrick == solid_brick) )
  {
    restoreLED(x, y+0); 
    restoreLED(x+1, y+0); 
    restoreLED(x+2, y+0);
    restoreLED(x, y+1); 
    restoreLED(x+1, y+1); 
    restoreLED(x+2, y+1);
  } else
  {
    /*
    switch(selectedBrick)
    {
      case horiz_wall_high: 
        drawBrick(x, y-1, horiz_wall_high); 
        break;    
      case horiz_wall_low:  
        drawBrick(x, y-1, horiz_wall_low); 
        break;
      case vert_wall:  
        drawBrick(x, y-1, vert_wall); 
        break;
      case corner_ne:  
        drawBrick(x, y-1, corner_ne); 
        break;
      case corner_nw:  
        drawBrick(x, y-1, corner_nw); 
        break;
      case corner_se:  
        drawBrick(x, y-1, corner_se); 
        break;
      case corner_sw:  
        drawBrick(x, y-1, corner_sw); 
        break;
    }
    */
    
    switch(selectedBrick)
    {
      case horiz_wall_high: 
        drawBrick(x, y, horiz_wall_high); 
        break;    
      case horiz_wall_low:  
        drawBrick(x, y, horiz_wall_low); 
        break;
      case vert_wall:  
        drawBrick(x, y, vert_wall); 
        break;
      case corner_ne:  
        drawBrick(x, y, corner_ne); 
        break;
      case corner_nw:  
        drawBrick(x, y, corner_nw); 
        break;
      case corner_se:  
        drawBrick(x, y, corner_se); 
        break;
      case corner_sw:  
        drawBrick(x, y, corner_sw); 
        break;
    }
   
  }
}

public void drawBrick(int x, int y, int selectedBrick)
{
  switch(selectedBrick)
  {
  case horiz_wall_low:  
    fill(empty);
    ellipse(50 + (x+0) * 27, 50 + (y+0) * 27, dotDiameter, dotDiameter);
    ellipse(50 + (x+1) * 27, 50 + (y+0) * 27, dotDiameter, dotDiameter);
    ellipse(50 + (x+2) * 27, 50 + (y+0) * 27, dotDiameter, dotDiameter);
    fill(solid);
    ellipse(50 + (x+0) * 27, 50 + (y+1) * 27, dotDiameter, dotDiameter);
    ellipse(50 + (x+1) * 27, 50 + (y+1) * 27, dotDiameter, dotDiameter);
    ellipse(50 + (x+2) * 27, 50 + (y+1) * 27, dotDiameter, dotDiameter);
    break;

  case horiz_wall_high: 
    fill(solid);
    ellipse(50 + (x+0) * 27, 50 + (y+0) * 27, dotDiameter, dotDiameter);
    ellipse(50 + (x+1) * 27, 50 + (y+0) * 27, dotDiameter, dotDiameter);
    ellipse(50 + (x+2) * 27, 50 + (y+0) * 27, dotDiameter, dotDiameter);
    fill(empty);
    ellipse(50 + (x+0) * 27, 50 + (y+1) * 27, dotDiameter, dotDiameter);
    ellipse(50 + (x+1) * 27, 50 + (y+1) * 27, dotDiameter, dotDiameter);
    ellipse(50 + (x+2) * 27, 50 + (y+1) * 27, dotDiameter, dotDiameter);
    break;

  case vert_wall:       
    fill(solid);
    ellipse(50 + (x+1) * 27, 50 + (y+0) * 27, dotDiameter, dotDiameter);
    ellipse(50 + (x+1) * 27, 50 + (y+1) * 27, dotDiameter, dotDiameter);
    fill(empty);
    ellipse(50 + (x+2) * 27, 50 + (y+0) * 27, dotDiameter, dotDiameter);
    ellipse(50 + (x+0) * 27, 50 + (y+0) * 27, dotDiameter, dotDiameter);
    ellipse(50 + (x+0) * 27, 50 + (y+1) * 27, dotDiameter, dotDiameter);
    ellipse(50 + (x+2) * 27, 50 + (y+1) * 27, dotDiameter, dotDiameter);
    break;

  case corner_nw:       
    fill(solid);
    ellipse(50 + (x+1) * 27, 50 + (y+0) * 27, dotDiameter, dotDiameter);
    ellipse(50 + (x+1) * 27, 50 + (y+1) * 27, dotDiameter, dotDiameter);
    ellipse(50 + (x+2) * 27, 50 + (y+0) * 27, dotDiameter, dotDiameter);
    fill(empty);
    ellipse(50 + (x+0) * 27, 50 + (y+0) * 27, dotDiameter, dotDiameter);
    ellipse(50 + (x+0) * 27, 50 + (y+1) * 27, dotDiameter, dotDiameter);
    ellipse(50 + (x+2) * 27, 50 + (y+1) * 27, dotDiameter, dotDiameter);
    break;

  case corner_ne:      
    fill(solid);
    ellipse(50 + (x+1) * 27, 50 + (y+0) * 27, dotDiameter, dotDiameter);
    ellipse(50 + (x+1) * 27, 50 + (y+1) * 27, dotDiameter, dotDiameter);
    ellipse(50 + (x+0) * 27, 50 + (y+0) * 27, dotDiameter, dotDiameter);
    fill(empty);
    ellipse(50 + (x+2) * 27, 50 + (y+0) * 27, dotDiameter, dotDiameter);
    ellipse(50 + (x+0) * 27, 50 + (y+1) * 27, dotDiameter, dotDiameter);
    ellipse(50 + (x+2) * 27, 50 + (y+1) * 27, dotDiameter, dotDiameter);
    break;

  case corner_sw:       
    fill(solid);
    ellipse(50 + (x+1) * 27, 50 + (y+0) * 27, dotDiameter, dotDiameter);
    ellipse(50 + (x+1) * 27, 50 + (y+1) * 27, dotDiameter, dotDiameter);
    ellipse(50 + (x+2) * 27, 50 + (y+1) * 27, dotDiameter, dotDiameter);
    fill(empty);
    ellipse(50 + (x+0) * 27, 50 + (y+0) * 27, dotDiameter, dotDiameter);
    ellipse(50 + (x+2) * 27, 50 + (y+0) * 27, dotDiameter, dotDiameter);
    ellipse(50 + (x+0) * 27, 50 + (y+1) * 27, dotDiameter, dotDiameter);
    break;

  case corner_se:       
    fill(solid);
    ellipse(50 + (x+1) * 27, 50 + (y+0) * 27, dotDiameter, dotDiameter);
    ellipse(50 + (x+1) * 27, 50 + (y+1) * 27, dotDiameter, dotDiameter);
    ellipse(50 + (x+0) * 27, 50 + (y+1) * 27, dotDiameter, dotDiameter);
    fill(empty);
    ellipse(50 + (x+2) * 27, 50 + (y+1) * 27, dotDiameter, dotDiameter);
    ellipse(50 + (x+0) * 27, 50 + (y+0) * 27, dotDiameter, dotDiameter);
    ellipse(50 + (x+2) * 27, 50 + (y+0) * 27, dotDiameter, dotDiameter);
    break;
  }
}

public void drawMouseCursor(int x, int y)
{
  int brickColor = 0;

  // Draw currently selected brick as mouse cursor

  // Bricks
  if ((selectedBrick == red_brick) || (selectedBrick == green_brick) || (selectedBrick == blue_brick) || (selectedBrick == violet_brick) || 
    (selectedBrick == yellow_brick) || (selectedBrick == orange_brick) || (selectedBrick == empty_brick) || (selectedBrick == solid_brick))
  {
    switch(selectedBrick)
    {
    case red_brick:    
      brickColor = red; 
      break;
    case green_brick:  
      brickColor = green; 
      break;
    case blue_brick:   
      brickColor = blue; 
      break;
    case yellow_brick:   
      brickColor = yellow; 
      break;
    case violet_brick:  
      brickColor = violet; 
      break;
    case orange_brick:  
      brickColor = orange; 
      break;
    case empty_brick:  
      brickColor = empty; 
      break;
    case solid_brick:
      brickColor = solid;
      break;
    }
    fill(brickColor);

    ellipse(50 + (x+0) * 27, 50 + (y+0) * 27, dotDiameter, dotDiameter);
    ellipse(50 + (x+1) * 27, 50 + (y+0) * 27, dotDiameter, dotDiameter);
    ellipse(50 + (x+2) * 27, 50 + (y+0) * 27, dotDiameter, dotDiameter);
    ellipse(50 + (x+0) * 27, 50 + (y+1) * 27, dotDiameter, dotDiameter);
    ellipse(50 + (x+1) * 27, 50 + (y+1) * 27, dotDiameter, dotDiameter);
    ellipse(50 + (x+2) * 27, 50 + (y+1) * 27, dotDiameter, dotDiameter);
  } else // Solid bricks
  {
    switch(selectedBrick)
    {
    case horiz_wall_high: 
      drawBrick(x, y, horiz_wall_high); 
      break;    
    case horiz_wall_low:  
      drawBrick(x, y, horiz_wall_low); 
      break;
    case vert_wall:  
      drawBrick(x, y, vert_wall); 
      break;
    case corner_ne:  
      drawBrick(x, y, corner_ne); 
      break;
    case corner_nw:  
      drawBrick(x, y, corner_nw); 
      break;
    case corner_se:  
      drawBrick(x, y, corner_se); 
      break;
    case corner_sw:  
      drawBrick(x, y, corner_sw); 
      break;
    }
  }
}

public void mouseMoved()
{
  // If the mouse is over the LED matrix panel, highlight the respective LED
  if ((mouseX > 36) && (mouseX < 845) && (mouseY > 36) && (mouseY < 845))
  {
    if (!diskOperationPending)
    {
      fill(6, 6, 6);
      noStroke();
      rect(39, 920, 900, 40);
    }
  } 

  if ((exportButton != null) && ((!exportButton.mouseOver(mouseX, mouseY)) && (exportButton.hasFocus))) { 
    exportButton.hasFocus = false; 
    exportButton.pressed = false; 
    exportButton.repaint();
  }
  if ((clearButton != null) && ((!clearButton.mouseOver(mouseX, mouseY)) && (clearButton.hasFocus))) { 
    clearButton.hasFocus = false; 
    clearButton.pressed = false; 
    clearButton.repaint();
  }
  if ((loadButton != null) && ((!loadButton.mouseOver(mouseX, mouseY)) && (loadButton.hasFocus))) { 
    loadButton.hasFocus = false; 
    loadButton.pressed = false; 
    loadButton.repaint();
  }
}

public void mousePressed() 
{
  // React only if the mouse is over the LED matrix panel
  if ((mouseX > 36) && (mouseX < 865) && (mouseY > 36) && (mouseY < 845))
  {
    if (mouseButton == LEFT)
    {
      if (  (((currentLED_x+2) % 3 == 0) && ((currentLED_y+1) % 2 == 0)) ||
        (((currentLED_x+2) % 3 == 1) && ((currentLED_y+1) % 2 == 0)) ||
        (((currentLED_x+2) % 3 == 2) && ((currentLED_y+1) % 2 == 0)) ||
        (((currentLED_x+2) % 3 == 0) && ((currentLED_y+1) % 2 == 1)) ||
        (((currentLED_x+2) % 3 == 1) && ((currentLED_y+1) % 2 == 1)) ||
        (((currentLED_x+2) % 3 == 2) && ((currentLED_y+1) % 2 == 1))
        )
      {
        int grid_x = 1+((currentLED_x-1)/3)*3;
        int grid_y = 1+((currentLED_y-1)/2)*2;
        //println("\ncurrentLED_y = "+currentLED_y);
        //println("\ngrid_y = "+grid_y);

        noStroke();

        frameBuffer[grid_x+0][grid_y+0] = selectedBrick;
        frameBuffer[grid_x+1][grid_y+0] = selectedBrick;
        frameBuffer[grid_x+2][grid_y+0] = selectedBrick;
        frameBuffer[grid_x+0][grid_y+1] = selectedBrick;
        frameBuffer[grid_x+1][grid_y+1] = selectedBrick;
        frameBuffer[grid_x+2][grid_y+1] = selectedBrick;
      }
    }
  }
  else
  {
  if ((redBrick != null) && (redBrick.mouseOver(mouseX, mouseY))) { 
    redBrick.select(); 
    selectedBrick = red_brick;
  }
  if ((greenBrick != null) && (greenBrick.mouseOver(mouseX, mouseY))) { 
    greenBrick.select(); 
    selectedBrick = green_brick;
  }
  if ((blueBrick != null) && (blueBrick.mouseOver(mouseX, mouseY))) { 
    blueBrick.select(); 
    selectedBrick = blue_brick;
  }
  if ((yellowBrick != null) && (yellowBrick.mouseOver(mouseX, mouseY))) { 
    yellowBrick.select(); 
    selectedBrick = yellow_brick;
  }
  if ((violetBrick != null) && (violetBrick.mouseOver(mouseX, mouseY))) { 
    violetBrick.select(); 
    selectedBrick = violet_brick;
  }
  if ((orangeBrick != null) && (orangeBrick.mouseOver(mouseX, mouseY))) { 
    orangeBrick.select(); 
    selectedBrick = orange_brick;
  }
  if ((solidBrick != null) && (solidBrick.mouseOver(mouseX, mouseY))) { 
    solidBrick.select(); 
    selectedBrick = solid_brick;
  }
  if ((emptyBrick != null) && (emptyBrick.mouseOver(mouseX, mouseY))) { 
    emptyBrick.select(); 
    selectedBrick = empty_brick;
  }
  if ((horizWallLow != null) && (horizWallLow.mouseOver(mouseX, mouseY))) { 
    horizWallLow.select(); 
    selectedBrick = horiz_wall_low;
  }
  if ((horizWallHigh != null) && (horizWallHigh.mouseOver(mouseX, mouseY))) { 
    horizWallHigh.select(); 
    selectedBrick = horiz_wall_high;
  }
  if ((vertWall != null) && (vertWall.mouseOver(mouseX, mouseY))) { 
    vertWall.select(); 
    selectedBrick = vert_wall;
  }
  if ((cornerNW != null) && (cornerNW.mouseOver(mouseX, mouseY))) { 
    cornerNW.select(); 
    selectedBrick = corner_nw;
  }
  if ((cornerNE != null) && (cornerNE.mouseOver(mouseX, mouseY))) { 
    cornerNE.select(); 
    selectedBrick = corner_ne;
  }
  if ((cornerSW != null) && (cornerSW.mouseOver(mouseX, mouseY))) { 
    cornerSW.select(); 
    selectedBrick = corner_sw;
  }
  if ((cornerSE != null) && (cornerSE.mouseOver(mouseX, mouseY))) { 
    cornerSE.select(); 
    selectedBrick = corner_se;
  }

  if ((exportButton != null) && (exportButton.mouseOver(mouseX, mouseY))) 
  { 
    exportButton.pressed = true; 
    exportButton.repaint();
    diskOperationPending = true;

    SwingUtilities.invokeLater(new Runnable() 
    {
      public void run() 
      {
        selectOutput("Select output file:", "outputFileSelected");
      }
    } 
    );

    fileLoaded = true;
  }

  if ((clearButton != null) && (clearButton.mouseOver(mouseX, mouseY))) 
  { 
    clearButton.pressed = true; 
    clearButton.repaint(); 
    drawEmptyMatrix(); 
    clearLEDmePlayFrameBuffer(); 
    
    upperBorder = true; 
    lowerBorder = true; 
    leftBorder = true; 
    rightBorder = true;
    upperGap.pressed = false; 
    upperGap.repaint();
    lowerGap.pressed = false; 
    lowerGap.repaint();
    leftGap.pressed = false; 
    leftGap.repaint();
    rightGap.pressed = false; 
    rightGap.repaint();
    
    upperBatActive = false;
    upperBatActiveSel.pressed = false;
    lowerBatActive = true;
    lowerBatActiveSel.pressed = true;
    leftBatActive = false;
    leftBatActiveSel.pressed = false;
    rightBatActive = false;
    rightBatActiveSel.pressed = false;

    drawArenaBorder(); 
    drawBats();
  }

  if ((upperBatActiveSel != null) && (upperBatActiveSel.mouseOver(mouseX, mouseY))) { 
    upperBatActiveSel.toggle();
  }
  if ((lowerBatActiveSel != null) && (lowerBatActiveSel.mouseOver(mouseX, mouseY))) { 
    lowerBatActiveSel.toggle();
  }
  if ((leftBatActiveSel != null) && (leftBatActiveSel.mouseOver(mouseX, mouseY))) { 
    leftBatActiveSel.toggle();
  }
  if ((rightBatActiveSel != null) && (rightBatActiveSel.mouseOver(mouseX, mouseY))) { 
    rightBatActiveSel.toggle();
  }

  if ((upperGap != null) && (upperGap.mouseOver(mouseX, mouseY))) { 
    upperGap.toggle();
  }
  if ((lowerGap != null) && (lowerGap.mouseOver(mouseX, mouseY))) { 
    lowerGap.toggle();
  }
  if ((leftGap != null) && (leftGap.mouseOver(mouseX, mouseY))) { 
    leftGap.toggle();
  }
  if ((rightGap != null) && (rightGap.mouseOver(mouseX, mouseY))) { 
    rightGap.toggle();
  }
  

  // If the last of the bats is being deselected, always activate the lower bat
  if ((!upperBatActiveSel.pressed) && (!lowerBatActiveSel.pressed) && (!leftBatActiveSel.pressed) && (!rightBatActiveSel.pressed)) 
  { 
    lowerBatActive = true;
    lowerBatActiveSel.pressed = true;
    upperBatActive = false;
    upperBatActiveSel.pressed = false;
    rightBatActiveSel.pressed = false;
    rightBatActiveSel.pressed = false;
    lowerBatActiveSel.repaint();
    upperBatActiveSel.repaint();
    leftBatActiveSel.repaint();
    rightBatActiveSel.repaint();
    
    drawFrameBuffer();
    drawBats();
  }
  
  if ((loadButton != null) && (loadButton.mouseOver(mouseX, mouseY))) 
  { 
    loadButton.pressed = true; 
    loadButton.repaint(); 
    diskOperationPending = true;
    SwingUtilities.invokeLater(new Runnable() 
    {
      public void run() 
      {
        selectInput("Select a file to load:", "inputFileSelected");
      }
    } 
    );

    fileLoaded = true;
  }
  }
}

public void mouseReleased()
{
  if ((mouseX > 36) && (mouseX < 845) && (mouseY > 36) && (mouseY < 845)) // Mouse is over the LED matrix panel
  {
  }

  if ((exportButton != null) && ((exportButton.mouseOver(mouseX, mouseY)) && (exportButton.pressed))) 
  { 
    exportButton.hasFocus = false; 
    exportButton.pressed = false; 
    SwingUtilities.invokeLater(new Runnable() 
    {
      public void run() 
      {
        exportButton.repaint();
      }
    } 
    );
  }

  if ((clearButton != null) && ((!clearButton.mouseOver(mouseX, mouseY)) && (clearButton.pressed))) 
  { 
    clearButton.hasFocus = false; 
    clearButton.pressed = false; 
    SwingUtilities.invokeLater(new Runnable() 
    {
      public void run() 
      {
        clearButton.repaint();
      }
    } 
    );
  }

  if ((loadButton != null) && ((!loadButton.mouseOver(mouseX, mouseY)) && (loadButton.pressed))) 
  { 
    loadButton.hasFocus = false; 
    loadButton.pressed = false; 
    SwingUtilities.invokeLater(new Runnable() 
    {
      public void run() 
      {
        loadButton.repaint();
      }
    } 
    );
  }
}

public void exportData(File path)
{   
  String dataMatrix = "";

  // Arena data
  String value = "";
  for (int j = 1; j < 30; j = j + 2)
  {
    for (int i = 1; i < 29; i = i + 3)
    { 
      switch (frameBuffer[i][j])
      {
      case RED_BRICK:  
        value = "2"; 
        break;
      case GREEN_BRICK:  
        value = "3"; 
        break;
      case BLUE_BRICK:  
        value = "4"; 
        break;
      case YELLOW_BRICK:  
        value = "6"; 
        break;
      case ORANGE_BRICK:  
        value = "8"; 
        break;
      case VIOLET_BRICK:  
        value = "5"; 
        break;
      case SOLID_BRICK:  
        value = "7"; 
        break;
      case EMPTY_BRICK:  
        value = "0"; 
        break;
      case HORIZ_WALL_LOW:  
        value = "15"; 
        break;
      case HORIZ_WALL_HIGH:  
        value = "9"; 
        break;
      case VERT_WALL:  
        value = "10"; 
        break;
      case CORNER_NW:  
        value = "11"; 
        break;
      case CORNER_NE:  
        value = "12"; 
        break;
      case CORNER_SW:  
        value = "14"; 
        break;
      case CORNER_SE:  
        value = "13"; 
        break;
      }
      dataMatrix = dataMatrix + value + ", ";

      if (i == 28) 
      { 
        dataMatrix = dataMatrix + "\r\n";
      }
    }
  }

  // Bats usage
  if (upperBatActive) { 
    dataMatrix += "1, ";
  } else { 
    dataMatrix += "0, ";
  }
  if (lowerBatActive) { 
    dataMatrix += "1, ";
  } else { 
    dataMatrix += "0, ";
  }
  if (leftBatActive) { 
    dataMatrix += "1, ";
  } else { 
    dataMatrix += "0, ";
  }
  if (rightBatActive) { 
    dataMatrix += "1, ";
  } else { 
    dataMatrix += "0,";
  }
  dataMatrix +=" // Upper, lower, left, right bat is 1-active/0 - inactive";
  dataMatrix += "\r\n";

  // Borders
  if (upperBorder) { 
    dataMatrix += "0, ";
  } else { 
    dataMatrix += "1, ";
  }
  if (lowerBorder) { 
    dataMatrix += "0, ";
  } else { 
    dataMatrix += "1, ";
  }
  if (leftBorder) { 
    dataMatrix += "0, ";
  } else { 
    dataMatrix += "1, ";
  }
  if (rightBorder) { 
    dataMatrix += "0, ";
  } else { 
    dataMatrix += "1,";
  }
  dataMatrix +=" // Upper, lower, left, right border is 0-closed/1-open";
  dataMatrix += "\r\n\n";

  System.out.print("\n"+dataMatrix);

  // Write a file into the current work directory
  // Not any more: for simplicity, the file name is standardized:
  PrintWriter output = createWriter(path); 
  output.println(dataMatrix);
  output.flush();
  output.close();


  // Writes the data into the system clipboard
  StringSelection data = new StringSelection(dataMatrix);
  Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
  clipboard.setContents(data, data);

  diskOperationPending = false;
}


public void outputFileSelected(File selection)
{
  if (selection == null) 
  {
    println("File requester was closed, or user hit cancel.");
    return;
  } else
  {
    // Try to save the arena data to the specified output file
    exportData(selection);
  }
}

public void inputFileSelected(File selection) 
{
  if (selection == null) 
  {
    println("File requester was either closed, or the user hit cancel.");
  } else if (!selection.exists())
  {
    println("The file does not exist.");
  } else
  {
    // Try to load the palette in the selected file, if any
    println("Try to load Arena data from file " + selection.getAbsolutePath());

    String lines[] = loadStrings(selection);
    println("There are " + lines.length + " lines in the file");

    // The complete data we are looking for looks like this:
    /*
    0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 
     0, 0, 0, 4, 6, 7, 0, 11, 12, 0, 
     0, 0, 0, 7, 6, 7, 0, 14, 13, 0, 
     0, 0, 0, 7, 6, 4, 0, 0, 0, 0, 
     0, 0, 0, 4, 6, 7, 0, 0, 0, 0, 
     0, 0, 0, 7, 6, 7, 0, 0, 0, 0, 
     0, 7, 6, 6, 6, 6, 6, 7, 0, 0, 
     0, 0, 7, 6, 6, 6, 7, 0, 0, 0, 
     0, 0, 0, 7, 6, 7, 0, 0, 0, 0, 
     0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 
     0, 0, 0, 0, 0, 0, 0, 11, 12, 0, 
     0, 11, 12, 0, 0, 0, 0, 14, 13, 0, 
     0, 14, 13, 0, 0, 0, 0, 0, 0, 0, 
     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
     0, 1, 0, 1,  // Upper, lower, left, right bat is 1-active/0 - inactive
     0, 0, 0, 0,  // Upper, lower, left, right border is 0-closed/1-open
     */

    // Read fifteen lines with ten numerical entries each
    int row = 0;
    int brickID;
    String[] m = new String[lines.length];
    do // Verify that such a section exists
    {
      m = match(lines[row], "(\\d+,)+");
      row++;
    }
    while ((m == null) && (row < lines.length));

    if (m != null) // File contains lines of the required format
    {
      System.out.print("\nFile contains lines of the required format.");
      for (int i = 0; i < 15; i++) 
      { 
        //System.out.print("\n"+lines[i]); 
        String[] tokens = splitTokens(lines[i], ",");
        for (int j = 0; j < 10; j++) 
        { 
          //print(tokens[j] + ", "); print("\n");
          brickID = Integer.parseInt(trim(tokens[j]));
          //print("\nbrickID: "+brickID);
          switch(brickID)
          {
          case 0:  
            writeFrameBuffer(1+3*j, 1+2*i, EMPTY_BRICK); 
            break;
          case 1:  
            writeFrameBuffer(1+3*j, 1+2*i, EMPTY_BRICK); 
            break; // 1 is currently unassigned
          case 2:  
            writeFrameBuffer(1+3*j, 1+2*i, RED_BRICK); 
            break;
          case 3:  
            writeFrameBuffer(1+3*j, 1+2*i, GREEN_BRICK); 
            break;
          case 4:  
            writeFrameBuffer(1+3*j, 1+2*i, BLUE_BRICK); 
            break;
          case 5:  
            writeFrameBuffer(1+3*j, 1+2*i, VIOLET_BRICK); 
            break;
          case 6:  
            writeFrameBuffer(1+3*j, 1+2*i, YELLOW_BRICK); 
            break;
          case 7:  
            writeFrameBuffer(1+3*j, 1+2*i, SOLID_BRICK); 
            break;
          case 8:  
            writeFrameBuffer(1+3*j, 1+2*i, ORANGE_BRICK); 
            break;
          case 9:  
            writeFrameBuffer(1+3*j, 1+2*i, HORIZ_WALL_HIGH); 
            break;
          case 10: 
            writeFrameBuffer(1+3*j, 1+2*i, VERT_WALL); 
            break;
          case 11: 
            writeFrameBuffer(1+3*j, 1+2*i, CORNER_NW); 
            break;
          case 12: 
            writeFrameBuffer(1+3*j, 1+2*i, CORNER_NE); 
            break;
          case 13: 
            writeFrameBuffer(1+3*j, 1+2*i, CORNER_SE); 
            break;
          case 14: 
            writeFrameBuffer(1+3*j, 1+2*i, CORNER_SW); 
            break;
          case 15: 
            writeFrameBuffer(1+3*j, 1+2*i, HORIZ_WALL_LOW); 
            break;
          }
        }
      }

      // Read lines with data for bats and borders

      //row muss richtig bestimmt werden
      /*
      do // Verify that such a section exists
       {
       m = match(lines[row], "(\\d+,)+");
       row++;
       }
       while ((m == null) && (row < lines.length));
       */
      
      // Bats
      row = 15;
      //println("Bats:");
      String[] tokens = splitTokens(lines[row], ",");
      //println(tokens);
      if (Integer.parseInt(trim(tokens[0])) == 0) { 
        upperBatActive = false;
        upperBatActiveSel.pressed = false;
      } else { 
        upperBatActive = true;
        upperBatActiveSel.pressed = true;
      }
      if (Integer.parseInt(trim(tokens[1])) == 0) { 
        lowerBatActive = false;
        lowerBatActiveSel.pressed = false;
      } else { 
        lowerBatActive = true;
        lowerBatActiveSel.pressed = true;
        
      }
      if (Integer.parseInt(trim(tokens[2])) == 0) { 
        leftBatActive = false;
        leftBatActiveSel.pressed = false;
      } else { 
        leftBatActive = true;
        leftBatActiveSel.pressed = true;
      }
      if (Integer.parseInt(trim(tokens[3])) == 0) { 
        rightBatActive = false;
        rightBatActiveSel.pressed = false;
      } else { 
        rightBatActive = true;
        rightBatActiveSel.pressed = true;
      }
      
      // Border parts
      row = 16;
      tokens = splitTokens(lines[row], ",");
      
      if (Integer.parseInt(trim(tokens[0])) == 1) { 
        upperBorder = false; 
        upperGap.pressed = true;
      } else { 
        upperBorder = true; 
        upperGap.pressed = false;
      }
      if (Integer.parseInt(trim(tokens[1])) == 1) { 
        lowerBorder = false; 
        lowerGap.pressed = true;
      } else { 
        lowerBorder = true; 
        lowerGap.pressed = false;
      }
      if (Integer.parseInt(trim(tokens[2])) == 1) { 
        leftBorder = false; 
        leftGap.pressed = true;
      } else { 
        leftBorder = true; 
        leftGap.pressed = false;
      }
      if (Integer.parseInt(trim(tokens[3])) == 1) { 
        rightBorder = false; 
        rightGap.pressed = true;
      } else { 
        rightBorder = true; 
        rightGap.pressed = false;
      }

      SwingUtilities.invokeLater(new Runnable() 
      {
        public void run() 
        {
          // Redraw bat selector buttons
          upperBatActiveSel.repaint();
          lowerBatActiveSel.repaint();
          leftBatActiveSel.repaint();
          rightBatActiveSel.repaint();
          
          // Redraw border selector buttons
          upperGap.repaint();
          lowerGap.repaint();
          leftGap.repaint();
          rightGap.repaint();

          drawArenaBorder(); 
        }
      } 
      );

      fileLoaded = true;
      redraw();
    } // File contains lines of the required format
  }
  diskOperationPending = false;
}

public void writeFrameBuffer(int i, int j, int brickID)
{
  frameBuffer[i+0][j+0] = brickID;
  frameBuffer[i+1][j+0] = brickID;
  frameBuffer[i+2][j+0] = brickID;
  frameBuffer[i+0][j+1] = brickID;
  frameBuffer[i+1][j+1] = brickID;
  frameBuffer[i+2][j+1] = brickID;
}

// ----------------------------------------------------------------------------------

public class ElementSelector implements SelectionTool
{
  private int type;
  private int x;
  private int y;
  private int w;
  private int h;
  public boolean selected;
  private ToolGroup group;

  public boolean mouseOver(int mx, int my)
  {
    // (mx, my) = mouse cursor position
    if ((mx >= x) && (mx <= x+w) && (my >= y) && (my <= y+h)) { 
      repaint(); 
      return true;
    }
    //hasFocus = false;
    return false;
  }

  public ElementSelector(int type, int x, int y, int w, int h)
  {
    this.x = x;
    this.y = y;
    this.w = w;
    this.h = h;
    this.type = type;
    //hasFocus = false;
    selected = false;
    repaint();
  }

  public void repaint()
  {
    int c = darkGrey;
    //int f = black;
    strokeWeight(2);
    if (!selected)
    {
      switch(type)
      {
      case RED_BRICK: 
        c = red; 
        break;
      case GREEN_BRICK: 
        c = green; 
        break;
      case BLUE_BRICK: 
        c = blue; 
        break;
      case YELLOW_BRICK: 
        c = yellow; 
        break;
      case VIOLET_BRICK: 
        c = violet; 
        break;
      case ORANGE_BRICK: 
        c = orange; 
        break;
      case SOLID_BRICK: 
        c = solid; 
        break;
      case EMPTY_BRICK: 
        c = empty; 
        break;
      case HORIZ_WALL_HIGH: 
        c = empty; 
        break;
      case HORIZ_WALL_LOW: 
        c = empty; 
        break;
      case VERT_WALL: 
        c = empty; 
        break;
      case CORNER_NW: 
        c = empty; 
        break;
      case CORNER_NE: 
        c = empty; 
        break;
      case CORNER_SW: 
        c = empty; 
        break;
      case CORNER_SE: 
        c = empty; 
        break;
      }
      fill(2);
      stroke(contourCol);
      strokeWeight(2);
      rect(x, y, 75, 50);
      noStroke();
      fill(c);
      ellipse(x+12, y+12, 12, 12);
      ellipse(x+37, y+12, 12, 12);
      ellipse(x+62, y+12, 12, 12);
      ellipse(x+12, y+37, 12, 12);
      ellipse(x+37, y+37, 12, 12);
      ellipse(x+62, y+37, 12, 12);
    } else
    {
      noFill();
      stroke(red);
      rect(x, y, 75, 50);
      noStroke();
    }

    // Draw the components that are not bricks
    fill(solid);
    switch(type)
    {
    case HORIZ_WALL_HIGH: 
      ellipse(x+12, y+12, 12, 12);
      ellipse(x+37, y+12, 12, 12);
      ellipse(x+62, y+12, 12, 12);
      break;
    case HORIZ_WALL_LOW:  
      ellipse(x+12, y+37, 12, 12);
      ellipse(x+37, y+37, 12, 12);
      ellipse(x+62, y+37, 12, 12);
      ; 
      break;
    case VERT_WALL:       
      ellipse(x+37, y+12, 12, 12);               
      ellipse(x+37, y+37, 12, 12);
      break;
    case CORNER_NE:       
      ellipse(x+12, y+12, 12, 12);
      ellipse(x+37, y+12, 12, 12);
      ellipse(x+37, y+37, 12, 12);
      break;
    case CORNER_NW:       
      ellipse(x+37, y+12, 12, 12);
      ellipse(x+62, y+12, 12, 12);
      ellipse(x+37, y+37, 12, 12);
      break;
    case CORNER_SE:       
      ellipse(x+37, y+12, 12, 12);
      ellipse(x+37, y+37, 12, 12);
      ellipse(x+12, y+37, 12, 12);
      break;
    case CORNER_SW:       
      ellipse(x+37, y+12, 12, 12);
      ellipse(x+37, y+37, 12, 12);
      ellipse(x+62, y+37, 12, 12);
      break;
    }
  }

  public void select() { 
    this.selected = true; 
    group.toggle(this); 
    repaint();
  }
  public void unselect() { 
    this.selected = false; 
    repaint();
  }

  public void setToolGroup(ToolGroup group)
  {
    this.group = group;
    group.register(this);
  }
}

interface SelectionTool
{
  int x = 0;
  int y = 0;
  int w = 0;
  int h = 0;
  public boolean selected = false;

  public void repaint();
  public void select();
  public void unselect();
  public boolean mouseOver(int x, int y);
  public void setToolGroup(ToolGroup t);
}

class ToolGroup
{
  Vector<SelectionTool> group = new Vector<SelectionTool>();

  public void register(SelectionTool tool)
  {
    group.add(tool);
  }

  public Vector<SelectionTool> getTools()
  {
    return group;
  }

  public void toggle(SelectionTool tool) // Activates tool, inactivates the other tools 
  {
    for (SelectionTool t : group)
    {
      //tool.unselect();
      tool.repaint();
      if (t != tool) { 
        t.unselect(); 
        t.repaint();
      }
    }
  }
}


public class BatSelectButton
{
  private int x;
  private int y;
  private int w;
  private int h;
  private int type;
  private String text;
  private boolean pressed;
  public boolean hasFocus;

  public boolean mouseOver(int mx, int my)
  {
    // (mx, my) = mouse cursor position
    if ((mx >= x) && (mx <= x+w) && (my >= y) && (my <= y+h)) { 
      hasFocus = true; 
      repaint(); 
      return true;
    }
    return false;
  }

  public BatSelectButton(int type, int x, int y, int w, int h, boolean usesBat)
  {
    this.x = x;
    this.y = y;
    this.w = w;
    this.h = h;
    this.pressed = usesBat;
    this.type = type;
    if (type == UPPER_BAT) { 
      this.text = "Upper Bat";
    } else if (type == LOWER_BAT) { 
      this.text = "Lower Bat";
    } else if (type == LEFT_BAT) { 
      this.text = "Left Bat";
    } else if (type == RIGHT_BAT) { 
      this.text = "Right Bat";
    }
    hasFocus = false;

    repaint();
    redraw();
  }

  public void toggle() 
  { 
    pressed = !pressed;
    repaint();

    if      (type == UPPER_BAT) { 
      upperBatActive = !upperBatActive;
    } else if (type == LOWER_BAT) { 
      lowerBatActive = !lowerBatActive;
    } else if (type == LEFT_BAT) { 
      leftBatActive = !leftBatActive;
    } else if (type == RIGHT_BAT) { 
      rightBatActive = !rightBatActive;
    }

    drawBats();
    redraw();
  }

  public void repaint()
  {
    if (f == null) 
    { 
      f = loadFont("ArialMT-30.vlw");
    }
    textFont(f, 30);

    if (pressed)
    {
      stroke(0);
      strokeWeight(2);
      fill(black);
      rect(x, y, w, h);
      strokeWeight(1);
      fill(7);
      text(this.text, x+10, y+35);
    } else
    {
      stroke(contourCol);
      fill (7);
      strokeWeight(2);
      rect(x, y, w, h);
      strokeWeight(1);
      fill(0);
      text(this.text, x+10, y+35);
    }
  }
}

public class GapSelectButton
{
  private int x;
  private int y;
  private int w;
  private int h;
  private int type;
  private String text;
  public boolean pressed;
  public boolean hasFocus;

  public boolean mouseOver(int mx, int my)
  {
    // (mx, my) = mouse cursor position
    if ((mx >= x) && (mx <= x+w) && (my >= y) && (my <= y+h)) { 
      hasFocus = true; 
      repaint(); 
      return true;
    }
    return false;
  }

  public GapSelectButton(int type, int x, int y, int w, int h, boolean isOpen)
  {
    this.x = x;
    this.y = y;
    this.w = w;
    this.h = h;
    this.type = type;
    if      (type == UPPER_GAP) { 
      this.text = "Upper Border";
    } else if (type == LOWER_GAP) { 
      this.text = "Lower Border";
    } else if (type == LEFT_GAP) { 
      this.text = "Left Border";
    } else if (type == RIGHT_GAP) { 
      this.text = "Right Border";
    }
    this.hasFocus = false;
    this.pressed = isOpen;

    repaint();
    redraw();
  }

  public void toggle() 
  { 
    pressed = !pressed;
    repaint();

    if      (type == UPPER_GAP) { 
      upperBorder = !upperBorder;
    } else if (type == LOWER_GAP) { 
      lowerBorder = !lowerBorder;
    } else if (type == LEFT_GAP) { 
      leftBorder = !leftBorder;
    } else if (type == RIGHT_GAP) { 
      rightBorder = !rightBorder;
    }

    drawArenaBorder();
    redraw();
  }

  public void repaint()
  {
    if (f == null) { 
      f = loadFont("ArialMT-30.vlw");
    }
    textFont(f, 30);

    if (pressed)
    {
      stroke(0);
      strokeWeight(2);
      fill(black);
      rect(x, y, w, h);
      strokeWeight(1);
      fill(7);
      text(this.text, x+10, y+35);
    } else
    {
      stroke(contourCol);
      fill (7);
      strokeWeight(2);
      rect(x, y, w, h);
      strokeWeight(1);
      fill(0);
      text(this.text, x+10, y+35);
    }
  }
}

interface Selectable
{
  int x = 0;
  int y = 0;
  int w = 0;
  int h = 0;
  public boolean selected = false;

  public void repaint();
  public void select();
  public void unselect();
  public boolean mouseOver(int x, int y);
  public void setToolGroup(ToolGroup t);
}


public class ExportButton
{
  private int x;
  private int y;
  private int w;
  private int h;
  public boolean pressed;
  public boolean hasFocus;

  public boolean mouseOver(int mx, int my)
  {
    // (mx, my) = mouse cursor position
    if ((mx >= x) && (mx <= x+w) && (my >= y) && (my <= y+h)) { 
      hasFocus = true; 
      repaint(); 
      return true;
    }
    return false;
  }

  public ExportButton(int x, int y, int w, int h)
  {
    this.x = x;
    this.y = y;
    this.w = w;
    this.h = h;
    hasFocus = false;
    pressed = false;
    repaint();
  }

  public void repaint()
  {
    if (f == null) { 
      f = loadFont("ArialMT-30.vlw");
    }
    textFont(f, 30);

    if (pressed)
    {
      stroke(7);
      strokeWeight(2);
      fill(black);
      rect(x, y, w, h);
      strokeWeight(1);
      fill(7);
      text("Export", x+10, y+35); 

      pressed = false;
    } else
    {
      stroke(contourCol);
      fill (7);
      strokeWeight(2);
      rect(x, y, w, h);
      strokeWeight(1);
      fill(contourCol);
      text("Export", x+10, y+35);
    }
  }
}

public class ClearButton
{
  private int x;
  private int y;
  private int w;
  private int h;
  public boolean pressed;
  public boolean hasFocus;

  public boolean mouseOver(int mx, int my)
  {
    // (mx, my) = mouse cursor position
    if ((mx >= x) && (mx <= x+w) && (my >= y) && (my <= y+h)) { 
      hasFocus = true; 
      repaint(); 
      return true;
    }
    return false;
  }

  public ClearButton(int x, int y, int w, int h)
  {
    this.x = x;
    this.y = y;
    this.w = w;
    this.h = h;
    hasFocus = false;
    pressed = false;
    repaint();
  }

  public void repaint()
  {
    if (f == null) { 
      f = loadFont("ArialMT-30.vlw");
    }
    textFont(f, 30);

    if (pressed)
    {
      stroke(7);
      strokeWeight(2);
      fill(black);
      rect(x, y, w, h);
      strokeWeight(1);
      fill(7);
      text("Clear Arena", x+10, y+35); 

      pressed = false;
    } else
    {
      stroke(contourCol);
      fill (7);
      strokeWeight(2);
      rect(x, y, w, h);
      strokeWeight(1);
      fill(contourCol);
      text("Clear Arena", x+10, y+35);
    }
  }
}

public class LoadButton
{
  private int x;
  private int y;
  private int w;
  private int h;
  public boolean pressed;
  public boolean hasFocus;

  public boolean mouseOver(int mx, int my)
  {
    // (mx, my) = mouse cursor position
    if ((mx >= x) && (mx <= x+w) && (my >= y) && (my <= y+h)) { 
      hasFocus = true; 
      repaint(); 
      return true;
    }
    return false;
  }

  public LoadButton(int x, int y, int w, int h)
  {
    this.x = x;
    this.y = y;
    this.w = w;
    this.h = h;
    //hasFocus = false;
    pressed = false;
    repaint();
  }

  public void repaint()
  {
    textFont(f, 30);

    if (pressed)
    {
      stroke(7);
      strokeWeight(2);
      fill(black);
      rect(x, y, w, h);
      strokeWeight(1);
      fill(7);
      text("Load", x+10, y+35); 

      pressed = false;
    } else
    {
      stroke(contourCol);
      fill (7);
      strokeWeight(2);
      rect(x, y, w, h);
      strokeWeight(1);
      fill(contourCol);
      text("Load", x+10, y+35);
    }
  }
}
  public void settings() {  size(1371, 970);  noSmooth(); }
  static public void main(String[] passedArgs) {
    String[] appletArgs = new String[] { "WallNBallEdit" };
    if (passedArgs != null) {
      PApplet.main(concat(appletArgs, passedArgs));
    } else {
      PApplet.main(appletArgs);
    }
  }
}
