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

import generativedesign.*; 
import processing.opengl.*; 

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





// problem mit zu vielen autos bei ford & co. (mehrmals auf manu klicken?)

IAManager iaManager = null;  // interaction manager

Node centerNode;
//Attractor centerAttractor;
ArrayList originList = new ArrayList();  // ArrayList of Origin objects
DataMaxima dataMax = new DataMaxima();

int ringColor = color(255, 255, 255, 80);

// fonts
PFont carNodeFont;
PFont manuNodeFont;
PFont ringFont;
PFont centerFont;

static final int NODE_BOUNDARY = 10;

public void setup() {  
  // setup screen
  size(1024, 768, OPENGL);
  lights();
  smooth();
  fill(0);
  textMode(MODEL);

  // setup graphical stuff
  carNodeFont = loadFont("MyriadPro-BoldCond-14.vlw");    
  manuNodeFont = loadFont("MyriadPro-BoldCond-16.vlw");
  ringFont = loadFont("MyriadPro-BoldCond-18.vlw");
  centerFont = loadFont("MyriadPro-BoldCond-42.vlw");
  
  // read car data
  String[] carStrings = loadStrings("cars.txt");
  HashMap originsAndManus = new HashMap();  // String origins -> ArrayList String manufactures
  HashMap manusAndCars = new HashMap();  // String manufractures -> ArrayList Cars cars

  for (int i = 1; i < carStrings.length; i++) {
    Car c = new Car(carStrings[i]);
    c.nodeFont = carNodeFont;

    //     println(c);

    if (originsAndManus.containsKey(c.origin)) {  // add manufacture to existing origin
      ArrayList manusInOrigin = (ArrayList)originsAndManus.get(c.origin);

      if (!manusInOrigin.contains(c.manufacturer)) {
        manusInOrigin.add(c.manufacturer);
      }
    } 
    else {
      ArrayList manusInOrigin = new ArrayList();
      manusInOrigin.add(c.manufacturer);
      originsAndManus.put(c.origin, manusInOrigin);  // add new origin with first manufacturer in list
    }

    // aggregate data in hashmap
    if (manusAndCars.containsKey(c.manufacturer)) {  // add car to existing manufacturer
      ((ArrayList)manusAndCars.get(c.manufacturer)).add(c);
    } 
    else {
      ArrayList l = new ArrayList();
      l.add(c);
      manusAndCars.put(c.manufacturer, l);  // add new manufacturer with first car in list
    }
  }

  println("Loaded " + (carStrings.length - 1) + " cars");
  println("Loaded " + originsAndManus.size() + " origins");
  println("Loaded " + manusAndCars.size() + " manufacturers");

  // sort origin list by number of manufacturers in it
  Map sortedOrigins = sortByArrayListSize(originsAndManus); 

  //  for (Iterator it = sortedOrigins.entrySet().iterator(); it.hasNext();) {
  //    Map.Entry pair = (Map.Entry)it.next();
  //    
  //    String originName = (String)pair.getKey();
  //    ArrayList manusInOrigin = (ArrayList)pair.getValue();
  //    
  //    println("> Loaded origin: " + originName + " with " + manusInOrigin.size() + " manufacturers");
  //  }

  // create center objects
  centerNode = new Node(width/2, height/2, 0);
  centerNode.setStrength(-2);
  centerNode.setDamping(0.1f);
  centerNode.setBoundary(0, 0, 0, width, height, 0);

  //  centerAttractor = new Attractor(width/2, height/2, 0);
  //  centerAttractor.setMode(Attractor.SMOOTH);
  //  centerAttractor.setRadius(500);
  //  centerAttractor.setStrength(5);

  // process data in hashmaps and get Origin objects with Manufacturer objects with Cars in them
  Iterator it = sortedOrigins.entrySet().iterator();
  int originNum = 0;
  int ringWidth = 300 / sortedOrigins.size();

  Origin innerRing = new Origin("Cars", 100, ringColor);
  originList.add(innerRing);

  while (it.hasNext()) {
    Map.Entry pair = (Map.Entry)it.next();

    String originName = (String)pair.getKey();
    ArrayList manusInOrigin = (ArrayList)pair.getValue();
    println("> Loaded origin #" + originNum + ": " + originName + " with " + manusInOrigin.size() + " manufacturers");

    Manufacturer prevManu = null;
    Manufacturer firstManu = null;
    boolean isFirstManu = true;

    Origin curOrigin = new Origin(originName, 2 * (100 + originNum * ringWidth) + 100, ringColor);
    curOrigin.ringFont = ringFont;

    float manuRadiantIncr = 2 * PI / manusInOrigin.size();
    float manuRadiant = 0.0f;

    for (int i = 0; i < manusInOrigin.size(); i++) {
      String manuName = (String)manusInOrigin.get(i);
      //println(">> Loaded manu: " + manuName);

      ArrayList manuCars = (ArrayList)manusAndCars.get(manuName);

      Manufacturer manu = new Manufacturer(manuName, manuCars, centerNode);
      manu.nodeFont = manuNodeFont;

      //      int manuNodeX = (int)random(200, width - 200);
      //      int manuNodeY = (int)random(100, height - 100);
      int manuNodeX = width / 2 + (int)(cos(manuRadiant) * 10.0f);
      int manuNodeY = height / 2 + (int)(sin(manuRadiant) * 10.0f);
      manuRadiant += manuRadiantIncr;

      manu.createNode(manuNodeX, manuNodeY);
      manu.createSpringToCenter(100 + originNum * ringWidth);
      //      manu.createSpringToCenter(300);

      if (isFirstManu) {
        firstManu = manu;

        isFirstManu = false;
      } 
      else {  
        prevManu.nextManu = manu;
        //      prevManu.createSpringToNextManu(200);
      }

      curOrigin.manus.add(manu);
      prevManu = manu;
    }

    prevManu.nextManu = firstManu;
    //  prevManu.createSpringToNextManu(200);

    //    firstManu.showCars = true;

    originList.add(curOrigin);

    originNum++;

    // create Interaction manager
    iaManager = new IAManager(originList);
  }
}

public void mousePressed() {
  iaManager.press(mouseX, mouseY);
}

public void mouseClicked() {
  iaManager.click(mouseX, mouseY);
}

public void mouseDragged() {
  iaManager.drag(mouseX, mouseY);
}

public void mouseReleased() {
  iaManager.release(mouseX, mouseY);
}


public void draw() {
  background(0);
  drawContent();
  iaManager.drawWindows();
}

private void drawContent() {
  for (int i = 0; i < originList.size(); i++) {
    Origin o = (Origin)originList.get(i);

    o.update();
    o.draw();
  }
}

public boolean overCircle(int px, int py, int cx, int cy, int diameter) 
{
  float disX = cx - px;
  float disY = cy - py;
  if(sqrt(sq(disX) + sq(disY)) < diameter/2 ) {
    return true;
  } 
  
  return false;
}

public boolean overRect(int px, int py, int rx, int ry, int w, int h) 
{
  if (px >= rx && px <= rx + w && py >= ry && py <= ry + h) {
    return true;
  } 
  
  return false;
}

private Map sortByArrayListSize(Map map) {
  List list = new LinkedList(map.entrySet());
  Collections.sort(list, new Comparator() {
    public int compare(Object o1, Object o2) {
      ArrayList l1 = (ArrayList)(((Map.Entry) (o1)).getValue());
      ArrayList l2 = (ArrayList)(((Map.Entry) (o2)).getValue());
      Integer count1 = new Integer(l1.size());
      Integer count2 = new Integer(l2.size());

      return ((Comparable) count1).compareTo(count2);
    }
  }
  );

  Map result = new LinkedHashMap();
  for (Iterator it = list.iterator(); it.hasNext();) {
    Map.Entry entry = (Map.Entry)it.next();
    result.put(entry.getKey(), entry.getValue());
  }
  return result;
} 

class Car implements ClickableObject {
  // content properties:
  String car;
  String manufacturer;
  float consumption;  // in km / liter
  int cylinders;
  float displacement;
  float horsepower;
  float weight; // in kg
  float accel;  // in sec from 0 to 60 mph
  int year;
  String origin;

  // graphical properties:
  int sphereSize = 20;
  int objColor = color(255, 255, 255, 255);
  int textColor = color(0, 0, 0, 255);
  PFont nodeFont;
  float textW = 0;
  float textH = 14;

  // links to other objects in the group:
  Car nextCar = null;
  Manufacturer manuObj = null;
  InfoWindow infoWindow = null;

  // force-directed layout stuff:
  Node carNode = null;
  Spring springToNextCar = null;
  Spring springToManu = null;

  // interaction stuff:
  int lastClick = 0;  // in millis
  int clickTimeout = 500;
  int wasSetToVisible = 0;

  Car(String fLine) {
    // parse data line
    String[] data = splitTokens(fLine, "\t");

    int i = 0;
    car = data[i++];
    manufacturer = data[i++];
    consumption = PApplet.parseFloat(data[i++]) * 1.609f / 3.785f;  // mpg to km-p-l
    cylinders = PApplet.parseInt(data[i++]);
    displacement = PApplet.parseFloat(data[i++]) * 16.387f;
    horsepower = PApplet.parseFloat(data[i++]);
    weight = PApplet.parseFloat(data[i++]) * 0.4536f;
    accel = PApplet.parseFloat(data[i++]);
    year = PApplet.parseInt(data[i++]);
    origin = data[i++];

    // fix buick anomaly
    if (manufacturer.equals("buick")) {
      if (origin.toLowerCase().equals("american")) {
        manufacturer += " (us)";
      } 
      else {
        manufacturer += " (eu)";
      }
    }
    
    // set maxima
    if (consumption > dataMax.consumption) {
      dataMax.consumption = consumption;
    }
    
    if (cylinders > dataMax.cylinders) {
      dataMax.cylinders = cylinders;
    }
    
    if (displacement > dataMax.displacement) {
      dataMax.displacement = displacement;
    }
    
    if (horsepower > dataMax.horsepower) {
      dataMax.horsepower = horsepower;
    }
    
    if (weight > dataMax.weight) {
      dataMax.weight = weight;
    }
    
    if (accel > dataMax.accel) {
      dataMax.accel = accel;
    }
    
    if (year > dataMax.year) {
      dataMax.year = year;
    }

    // create info window
    infoWindow = new InfoWindow(this);
  }

  public void createNode(int x, int y) {
    carNode = new Node(x, y, 0);
    carNode.setStrength(-2);
    carNode.setDamping(0.1f);
    carNode.setBoundary(NODE_BOUNDARY, NODE_BOUNDARY, 0, width - NODE_BOUNDARY, height - NODE_BOUNDARY, 0);
  }

  public void createSpringToManu() {
    springToManu = new Spring(carNode, manuObj.manuNode);
    springToManu.setStiffness(0.7f);
    springToManu.setDamping(0.9f);
    springToManu.setLength(100);
  }

  public void createSpringToNextCar(int springLength) {
    springToNextCar = new Spring(carNode, nextCar.carNode);
    springToNextCar.setStiffness(0.7f);
    springToNextCar.setDamping(0.9f);
    springToNextCar.setLength(springLength);
  }
  
  public boolean clickAtPos(int px, int py) {
    // update interactions
    if (wasSetToVisible > 0 && overRect(px, py, (int)carNode.x - (int)textW/2, (int)carNode.y - (int)textH/2, (int)textW, (int)textH)) {
//      int now = millis();
//      if ((now - wasSetToVisible > clickTimeout) && (now - lastClick > clickTimeout)) {
        println("Click on " + car);

        if (!infoWindow.visible) {
          infoWindow.open((int)carNode.x, (int)carNode.y);
        }

//        lastClick = now;
//      }

      return true;
    }
    
    return false;
  }

  public void update() {
    // update attractions
    attractToNodes();

    // update springs
    if (springToManu != null) {
      springToManu.update();
    }

    if (springToNextCar != null) {
      springToNextCar.update();
    }

    // update nodes
    if (carNode != null) {
      carNode.update();
    }
    
    // update window
    if (infoWindow.visible) {
      infoWindow.update();
    }
  }

  public void draw() {
    drawSelf();
    drawDebug();

    //infoWindow.draw();
  }

  private void drawSelf() {
    noStroke();
    fill(objColor);

    pushMatrix();

    translate(carNode.x, carNode.y, 1);

    //ellipse(0, 0, sphereSize, sphereSize);

    String nodeTitle = car.toUpperCase();
    textW = textWidth(nodeTitle) + 2;
    
    fill(color(255, 255, 255, 255));
    rectMode(CENTER);
    rect(0, 0, textW, textH);

    fill(color(0, 0, 0, 255));
    textFont(nodeFont, 14);
    textAlign(CENTER);
    text(nodeTitle, 0, 5);
    

    popMatrix();
  }

  private void drawDebug() {
    // draw springs
    stroke(255, 255, 0, 255);
    strokeWeight(2);

    if (carNode != null && manuObj != null && springToManu != null) {
      line(carNode.x, carNode.y, 0, manuObj.manuNode.x, manuObj.manuNode.y, 0);
    }

    if (carNode != null && nextCar != null && springToNextCar != null) {
      line(carNode.x, carNode.y, 0, nextCar.carNode.x, nextCar.carNode.y, 0);
    }
  }

  private void attractToNodes() {
    if (carNode != null && manuObj != null) {
      carNode.attract(manuObj.manuNode);
      manuObj.manuNode.attract(carNode);
    }

    if (carNode != null && nextCar != null) {
      carNode.attract(nextCar.carNode);
      nextCar.carNode.attract(carNode);
    }
  }

  public String toString() {
    String s = "Car: ";

    s += car + ",";
    s += manufacturer + ",";
    s += consumption + ",";
    s += cylinders + ",";
    s += displacement + ",";
    s += horsepower + ",";
    s += weight + ",";
    s += accel + ",";
    s += year + ",";
    s += origin;

    return s;
  }
}

class DataMaxima {
  float consumption;  // in km / liter
  int cylinders;
  float displacement;
  float horsepower;
  float weight; // in kg
  float accel;  // in sec from 0 to 60 mph
  int year;
  
  DataMaxima() {
    consumption = Float.MIN_VALUE;
    cylinders = Integer.MIN_VALUE;
    displacement = Float.MIN_VALUE;
    horsepower = Float.MIN_VALUE;
    weight = Float.MIN_VALUE;
    accel = Float.MIN_VALUE;
    year = Integer.MIN_VALUE;
  }
}
interface DragableObject {
  public boolean startDragAtPos(int px, int py);
  public boolean dragAtPos(int px, int py);
}

interface ClickableObject {
  public boolean clickAtPos(int px, int py);
}

class IAManager {
  ArrayList originList = null;
  ArrayList windowList = new ArrayList();
  
  DragableObject curDragableObj = null;
  
  IAManager(ArrayList pOriginList) {
    originList = pOriginList;
  }
  
  public void registerWindow(InfoWindow wnd) {
    if (!windowList.contains(wnd)) {
      windowList.add(wnd);
    }
  }
  
  public void unregisterWindow(InfoWindow wnd) {
    if (windowList.contains(wnd)) {
      windowList.remove(windowList.indexOf(wnd));
    }
  }
  
  public void drawWindows() {
    for (int i = 0; i < windowList.size(); i++) {
      InfoWindow w = (InfoWindow)windowList.get(i);
      
      w.update();
      w.draw();
    }
  }
  
  public void click(int x, int y) {
    for (int i = 0; i < windowList.size(); i++) {
      InfoWindow w = (InfoWindow)windowList.get(i);
      
      if (w.clickAtPos(x, y)) {
        return;
      }
    }
    
    for (int i = 0; i < originList.size(); i++) {
      Origin o = (Origin)originList.get(i);
            
      for (int j = 0; j < o.manus.size(); j++) {
        Manufacturer m = (Manufacturer)o.manus.get(j);
        
        if (m.clickAtPos(x, y)) {
          return;
        }
        
        for (int k = 0; k < m.cars.size(); k++) {
          Car c = (Car)m.cars.get(k);
          
          if (c.clickAtPos(x, y)) {
            return;
          }
        }
      }
    }
  }
  
  public void press(int x, int y) {
    for (int i = 0; i < windowList.size(); i++) {
      InfoWindow w = (InfoWindow)windowList.get(i);
      
      if (w.startDragAtPos(x, y)) {
        curDragableObj = w;
        
        println("Start to drag with window#" + i);
        
        return;
      }
    }
  }
  
  public void release(int x, int y) {
    curDragableObj = null;
  }
  
  public void drag(int x, int y) {
    if (curDragableObj != null) {
      curDragableObj.dragAtPos(x, y);
    }
  }
}
class InfoWindow implements DragableObject, ClickableObject {
  // data:
  Car car;

  // status:
  boolean visible = false;

  // position and size:
  int x;
  int y;
  int w = 400;
  int h = 170;
  int headerH = 30;
  
  int closeX = headerH/2 + 1;
  int closeY = headerH/2;
  int closeCircleDia = headerH - 5;
  
  int startDragX;
  int startDragY;

  // appearance:
  int borderColor = color(255, 255, 255, 192);
  int bgColor = color(0, 0, 0, 196);
  int closeButtonColor = color(40, 40, 40, 255);
  int headerColor = color(255, 255, 255, 64);
  int headerTextColor = color(255, 255, 255, 255);
  int contentTextColor = color(255, 255, 255, 255);
  PFont headerFont;
  PFont contentFont;

  InfoWindow(Car pCar) {
    car = pCar;
    
    headerFont = manuNodeFont;
    contentFont = carNodeFont;
  }
  
  public boolean startDragAtPos(int px, int py) {
    if (overRect(px, py, x, y, w, headerH)) {
      if (overCircle(px, py, x + closeX - closeCircleDia / 2, y + closeY, closeCircleDia + 5)) {
        this.close();
        
        return true;
      }
      
      startDragX = px - x;
      startDragY = py - y;
      
      return true;
    }
    
    return false;
  }
  
  public boolean dragAtPos(int px, int py) {
    x = px  - startDragX;
    y = py - startDragY;
    
    return false;
  }
  
  public boolean clickAtPos(int px, int py) {
    if (overRect(px, py, x, y, w, h)) {
      if (overCircle(px, py, x + closeX - closeCircleDia / 2, y + closeY, closeCircleDia + 5)) {
        this.close();
      }
      
      return true;
    }

    return false;
  }
  
  public void open(int px, int py) {
    if (visible) return;
    
    visible = true;
    setAtCenterPos(px, py);
    iaManager.registerWindow(this);
  }
  
  public void close() {
    if (!visible) return;
    
    visible = false;
    iaManager.unregisterWindow(this);
  }

  public void setAtCenterPos(int cX, int cY) {
    x = cX - w / 2;
    y = cY - h / 2;
    
    if (y < 0) {
      y = 0;
    }
  }

  public void draw() {
    if (visible) {
      pushMatrix();

      translate(x, y, 20);

      drawWindow();
      drawHeader();
      drawContent();

      popMatrix();
    }
  }

  public void update() {
    
  }

  private void drawWindow() {
    strokeWeight(1);
    stroke(borderColor);
    fill(bgColor);
    rectMode(CORNER);

    rect(0, 0, w, h);
  }

  private void drawHeader() {
    noStroke();
    fill(headerColor);
    rectMode(CORNER);
    rect(0, 0, w, headerH);
    
    fill(headerTextColor);
    textFont(headerFont, 16);
    textAlign(CENTER);
    text((car.manufacturer + " " + car.car + " ('" + (int)car.year + ")").toUpperCase(), w / 2, headerH / 2 + 5);
    
    ellipseMode(CENTER);
    fill(closeButtonColor);
    noStroke();
    ellipse(closeX, closeY, closeCircleDia, closeCircleDia);
    stroke(color(255, 255, 255, 255));
    strokeWeight(2);
    line(closeX - 5, closeY - 5, closeX + 5, closeY + 5);
    line(closeX - 5, closeY + 5, closeX + 5, closeY - 5);    
  }

  private void drawContent() {
    int barX = 10;
    int barY = headerH + 10;
    int barMaxW = 280;
    int barH = 15;
    int barBorder = 5;
    
    drawBar(barX, barY, barMaxW, barH, "Consuption", car.consumption, "km / liter", dataMax.consumption, 2);
    barY += (barH + barBorder);
    drawBar(barX, barY, barMaxW, barH, "Weight", car.weight, "kg", dataMax.weight, 2);
    barY += (barH + barBorder);
    drawBar(barX, barY, barMaxW, barH, "Displacement", car.displacement, "ccm", dataMax.displacement, 2);
    barY += (barH + barBorder);
    drawBar(barX, barY, barMaxW, barH, "Horsepower", car.horsepower, " HP", dataMax.horsepower, 0);
    barY += (barH + barBorder);
    drawBar(barX, barY, barMaxW, barH, "Cylinders", car.cylinders, "", dataMax.cylinders, 0);
    barY += (barH + barBorder);
    drawBar(barX, barY, barMaxW, barH, "Acceleration", car.accel, " sec", dataMax.accel, 2);
  }
  
  private void drawBar(int barX, int barY, int barMaxW, int barH, String title, float v, String unit, float vMax, int digitsAfterComma) {
    float perc = v / vMax;
    int barW = (int)(perc * barMaxW);
    int barColor = color(50 + 200.0f * perc, 250 - 200.0f * perc, 0);
    
    noStroke();
    
    fill(contentTextColor);
    textFont(contentFont, 14);
    textAlign(LEFT);
    text(title, barX, barY + 12);

    fill(barColor);
    rect(barX + 100, barY, barW, barH);
    
    fill(contentTextColor);
    textFont(contentFont, 14);
    textAlign(RIGHT);
    text(nf(v, 1, digitsAfterComma) + " " + unit, barX + 100 + barMaxW, barY + 12);
  }
}

class Manufacturer implements ClickableObject {
  // content properties:
  String name;

  // graphical properties:
  int sphereSize = 30;
  int objColor = color(255, 255, 255, 192);
  int textColor = color(0, 0, 0, 255);
  PFont nodeFont;

  // links to all objects in this group:
  Manufacturer nextManu;
  ArrayList cars;  // List with Car objects

    // force-directed layout stuff:
  Node centerNode = null;
  Node manuNode = null;
  Spring springToCenter = null;
  Spring springToNextManu = null;
  boolean showCars = false;
  int maxCarsPerGroup = 12;
  int curGroupNum = 0;
  int carStartNum = 0;
  int carEndNum = 0;
  int carNumGroups = 0;

  // interaction stuff:
  int lastClick = 0;  // in millis
  int clickTimout = 500;

  Manufacturer(String pName, ArrayList pCars, Node pCenterNode) {
    name = pName;
    centerNode = pCenterNode;

    if (pCars == null)
      cars = new ArrayList();
    else {
      cars = pCars;

      curGroupNum = -1;
      carNumGroups = (int)ceil(cars.size() / maxCarsPerGroup) + 1;

      // set links in cars
      for (int i = 0; i < cars.size(); i++) {
        Car c = (Car)cars.get(i);

        // set link to manufacturer
        c.manuObj = this;

        // set links to next car
        //        if (i + 1 < cars.size()) {
        //          c.nextCar = (Car)cars.get(i + 1);
        //        } 
        //        else {
        //          c.nextCar = (Car)cars.get(0);
        //        }
      }
    }
  }

  public void createNode(int x, int y) {
    manuNode = new Node(x, y, 0);
    manuNode.setStrength(-2);
    manuNode.setDamping(0.1f);
    manuNode.setBoundary(NODE_BOUNDARY, NODE_BOUNDARY, 0, width - NODE_BOUNDARY, height - NODE_BOUNDARY, 0);
    manuNode.setDiameter(sphereSize);

    // create car nodes
    //    Car c = null;

    for (int i = 0; i < cars.size(); i++) {
      Car c = (Car)cars.get(i);

      c.createNode(0, 0);    // initial positions are set in initCarNodePositions()
      c.createSpringToManu();

      //      if (i > 0) {
      //        Car prev = (Car)cars.get(i - 1);
      //        //prev.createSpringToNextCar(100);
      //      }
    }

    //c.createSpringToNextCar(100);
  }

  public void createSpringToCenter(int springLength) {
    springToCenter = new Spring(manuNode, centerNode);
    springToCenter.setStiffness(0.7f);
    springToCenter.setDamping(0.9f);
    springToCenter.setLength(springLength);
  }

  public void createSpringToNextManu(int springLength) {
    springToNextManu = new Spring(manuNode, nextManu.manuNode);
    springToNextManu.setStiffness(0.7f);
    springToNextManu.setDamping(0.9f);
    springToNextManu.setLength(springLength);
  }

  public boolean clickAtPos(int px, int py) {
    // update interactions
    if (overCircle(px, py, (int)manuNode.x, (int)manuNode.y, sphereSize)) {
      //      int now = millis();
      //      if (now - lastClick > clickTimout) {
      println("Click on " + name);
      
      // enable showing cars at the beginning
      if (curGroupNum < 0 && !showCars) {
        showCars = true;
      }
      
      // increase car group number
      curGroupNum++;
      
      // disable showing cars if the last car group was reached
      if (curGroupNum >= carNumGroups) {
        showCars = false;
        curGroupNum = -1;
      }
      
      // calculate start and end car numbers
      carStartNum = curGroupNum * maxCarsPerGroup;
      carEndNum = carStartNum + maxCarsPerGroup;
      if (carEndNum > cars.size()) {
        carEndNum = cars.size();
      }

      // init or hide car nodes
      if (showCars) {
        initCarNodePositions();
      } 
      else {
        hideCarNodes();
      }

      //        lastClick = now;
      //      }

      return true;
    }

    return false;
  }

  public void update() {    
    // update cars
    if (showCars) {
      for (int i = carStartNum; i < carEndNum; i++) {
        Car c = (Car)cars.get(i);

        c.update();
      }
    }

    // update attractions
    attractToNodes();

    // update springs
    if (springToCenter != null) {
      springToCenter.update();
    }

    if (springToNextManu != null) {
      springToNextManu.update();
    }

    // update nodes
    if (manuNode != null) {
      manuNode.update();
    }
  }

  public void draw() {
    // draw cars
    if (showCars) {
      for (int i = carStartNum; i < carEndNum; i++) {
        Car c = (Car)cars.get(i);

        c.draw();
      }
    }

    drawSelf();
    //drawDebug();
  }

  private void drawSelf() {
    noStroke();
    fill(objColor);

    pushMatrix();

    translate(manuNode.x, manuNode.y, 0);

    // draw circle
    fill(objColor);
    ellipse(0, 0, sphereSize, sphereSize);
    
    // draw group circle & number
    fill(objColor);

    ellipse(sphereSize / 2.25f, sphereSize / 2.25f, sphereSize / 1.15f, sphereSize / 1.15f);
    
    String numText;
    if (showCars) {
      numText = (carStartNum + 1) + "-" + carEndNum;
    } 
    else {
      numText = "" + cars.size();
    }

    fill(textColor);
    textFont(carNodeFont, 14);
    textAlign(CENTER);

    text(numText, sphereSize / 2.25f, sphereSize / 2.25f + 5);
    
    // draw manufacturer name
    fill(textColor);
    textFont(nodeFont, 16);
    textAlign(CENTER);
    text(name.toUpperCase(), 0, 5);

    popMatrix();
  }

  private void drawDebug() {
    // draw springs
    stroke(0, 255, 255, 255);
    strokeWeight(4);

    if (manuNode != null && centerNode != null && springToCenter != null) {
      line(manuNode.x, manuNode.y, 0, centerNode.x, centerNode.y, 0);
    }

    if (manuNode != null && nextManu != null && springToNextManu != null) {
      line(manuNode.x, manuNode.y, 0, nextManu.manuNode.x, nextManu.manuNode.y, 0);
    }
  }

  private void attractToNodes() {    
    if (manuNode != null && centerNode != null) {
      manuNode.attract(centerNode);
      centerNode.attract(manuNode);
    }

    if (manuNode != null && nextManu != null) {
      manuNode.attract(nextManu.manuNode);
      nextManu.manuNode.attract(manuNode);
    }
  }

  protected void initCarNodePositions() {
    int numCars = carEndNum - carStartNum;
    
    float carRadiantIncr = 2 * PI / numCars;
    float carRadiant = 0.0f;

    for (int i = carStartNum; i < carEndNum; i++) {
      Car c = (Car)cars.get(i);

      c.wasSetToVisible = millis();

      c.carNode.x = manuNode.x + (cos(carRadiant) * 10.0f);
      c.carNode.y = manuNode.y + (sin(carRadiant) * 10.0f);
      carRadiant += carRadiantIncr;
    }
  }

  protected void hideCarNodes() {
    for (int i = 0; i < cars.size(); i++) {
      Car c = (Car)cars.get(i);

      c.wasSetToVisible = 0;
    }
  }
}

class Origin {
  // content properties:
  String name;
  
  // graphical properties:
  int ringDiameter;
  int ringColor;
  PFont ringFont;
  int textColor = color(0, 0, 0, 255);
  
  // links to all objects in this group:
  ArrayList manus; // ArrayList with Manufacturer objects
  
  
  Origin(String pName, int pRingDiameter, int pRingColor) {
    println("> New origin " + pName + "(" + pRingDiameter + ")");
    
    name = pName;
    ringDiameter = pRingDiameter;
    ringColor = pRingColor;
    
    manus = new ArrayList();
  }
  
  public void update() {
    
  }
  
  public void draw() {
    fill(ringColor);
    noStroke();
    ellipse(width/2, height/2, ringDiameter, ringDiameter);
    
    if (name != null && name.length() > 0) {
      fill(textColor);
      if (ringDiameter <= 100) {
        textFont(centerFont, 48);
        
        stroke(textColor);
        strokeWeight(4);
        
        textAlign(CENTER);
        text(name.toUpperCase(), width/2, height/2 + 15);
      
//        for (int i = 0; i < 3; i++) {
//          textAlign(LEFT);
//          text(name.toUpperCase(), width/2 + ringDiameter / 2 - 100 + i * 10, height/2 - 5 + i * 23);
//        }
      } else {
        textFont(ringFont, 18);
        textAlign(CENTER);
        text(name.toUpperCase(), width/2 + ringDiameter / 2 - 55, height/2 - 30);
      }
    }
    
    for (int i = 0; i < manus.size(); i++) {
      Manufacturer m = (Manufacturer)manus.get(i);
      //centerAttractor.attract(m.manuNode);
      m.update();
      m.draw();
    }
  }
}
  static public void main(String args[]) {
    PApplet.main(new String[] { "--bgcolor=#FFFFFF", "Ue3_Konrad_markus" });
  }
}
