//import processing.opengl.*;

OSMMercator mercator;
float pixelsPerMinute;

PFont scaleFont;
PFont headerFont;

Hashtable stations = new Hashtable();
Hashtable connections = new Hashtable();
Hashtable routes = new Hashtable();

boolean showTimeBands = false;
Station selected = null;
Station ellipseCentre = null;
PImage ellipses = null;

void setup() {

  size(800,500);
  
  //size(screen.width,screen.height,OPENGL);
  //size(screen.width,screen.height);

  //  pixelsPerMinute = 5.0; // JAVA2D, 800, 500
  pixelsPerMinute = width/150.0;
  smooth(); // strange things happen with GL ellipses?

  scaleFont = loadFont("CenturyGothic-10b.vlw"); // limited chars
  headerFont = loadFont("CenturyGothic-24.vlw");
  
  parseStations();
  parseConnections();
  parseRoutes();

  double maxLon=Double.MIN_VALUE,minLon=Double.MAX_VALUE,maxLat=Double.MIN_VALUE,minLat=Double.MAX_VALUE;

  Iterator statIter = stations.values().iterator();
  while(statIter.hasNext()) {
    Station s = (Station)statIter.next();
    //   println(s);
    maxLon = Math.max(s.lon,maxLon);
    minLon = Math.min(s.lon,minLon);
    maxLat = Math.max(s.lat,maxLat);
    minLat = Math.min(s.lat,minLat);
  }
  //  println(maxLon+","+minLon+","+maxLat+","+minLat);

  mercator = new OSMMercator((minLat+maxLat)/2.0, (minLon+maxLon)/2.0, 1.05*(maxLon-minLon)/width, width, height);

  //pixelsPerMinute = 1.0/(float)mercator.kilometerinpixels();

  statIter = stations.values().iterator();
  while(statIter.hasNext()) {
    Station s = (Station)statIter.next();
    s.mapx = (float)mercator.x(s.lon);
    s.mapy = (float)mercator.y(s.lat);
    s.screenx = s.mapx;
    s.screeny = s.mapy;
    s.targetx = s.mapx;
    s.targety = s.mapy;
  }

  /*  Iterator conns = connections.values().iterator();
   while (conns.hasNext()) {
   Iterator lineIter = ((Collection)conns.next()).iterator();
   while(lineIter.hasNext()) {
   Connection c = (Connection)lineIter.next();
   Route r = (Route)routes.get(c.line);
   print(c.line + "\t" + c.a.id + "\t" + c.b.id + "\t" + r.name + "\t" + c.a.name + "\t" + c.b.name + "\n");
   }
   } */

  //  noLoop();

}


void draw() {

  background(255);
  smooth();
  
  if (showTimeBands) {
//    if (ellipses != null) {
//      image(ellipses,0,0);
//    }
//    else {
      ellipseMode(CENTER);
      for (float r = max(width,height), i=0; r > 0; r -= pixelsPerMinute*10, i++) {
        fill(i%2==0?230:255);
        noStroke();
        ellipse(ellipseCentre.screenx,ellipseCentre.screeny,2*r,2*r);
      }
//      ellipses = get(0,0,width,height);
//    }
  }

  Iterator statIter = stations.values().iterator();
  while(statIter.hasNext()) {
    Station s = (Station)statIter.next();
    s.animate();
  }

  Iterator conns = connections.values().iterator();
  while (conns.hasNext()) {
    Iterator lineIter = ((Collection)conns.next()).iterator();
    while(lineIter.hasNext()) {
      Connection c = (Connection)lineIter.next();
      Route r = (Route)routes.get(c.line);
      fill(r.colour);
      noStroke();
      ellipse(c.a.screenx,c.a.screeny,5.0,5.0);
      ellipse(c.b.screenx,c.b.screeny,5.0,5.0);
    }
  }

  conns = connections.values().iterator();
  while (conns.hasNext()) {
    Iterator lineIter = ((Collection)conns.next()).iterator();
    while(lineIter.hasNext()) {
      Connection c = (Connection)lineIter.next();
      Route r = (Route)routes.get(c.line);
      stroke(r.colour);
      strokeWeight(4.0);
      line(c.a.screenx,c.a.screeny,c.b.screenx,c.b.screeny);
    }
  }

  conns = connections.values().iterator();
  while (conns.hasNext()) {
    Iterator lineIter = ((Collection)conns.next()).iterator();
    while(lineIter.hasNext()) {
      Connection c = (Connection)lineIter.next();
      Route r = (Route)routes.get(c.line);
      if (r.stripe != 0) {
        stroke(r.stripe);
        strokeWeight(2.0);
        line(c.a.screenx,c.a.screeny,c.b.screenx,c.b.screeny);
      }
    }
  }

  float minDist = Float.MAX_VALUE;
  ellipseMode(CENTER);
  strokeWeight(2.0);
  statIter = stations.values().iterator();
  while(statIter.hasNext()) {
    Station s = (Station)statIter.next();
    float distance = dist(mouseX,mouseY,s.screenx,s.screeny);
    if (distance < minDist) {
      selected = s;
      minDist = distance;
    }
    if (s.totalLines-s.rail > 1) {
      stroke(0);
      fill(255);
      ellipse(s.screenx,s.screeny,5.0,5.0);
    }
  }

  if (selected != null) {
//    stroke(200);
//    strokeWeight(1.0);
//    line(mouseX,mouseY,selected.screenx,selected.screeny);
    stroke(255,255,0);
    fill(255,255,0);
    ellipse(selected.screenx,selected.screeny,5.0,5.0);
  }

  stroke(0);
  strokeWeight(1.0);
  pushMatrix();
  translate(width/20,height-(width/20));
  textAlign(CENTER);
  textFont(scaleFont,10);
  fill(0);
  if (showTimeBands) {
    line(0,0,0,5);
    text("0",0,15);
    line(5*pixelsPerMinute,0,5*pixelsPerMinute,5);
    text("5",5*pixelsPerMinute,15);
    line(10*pixelsPerMinute,0,10*pixelsPerMinute,5);
    text("10",10*pixelsPerMinute,15);
    line(0,5,10*pixelsPerMinute,5);
    text("minutes",5*pixelsPerMinute,25);
  }
  else {
    line(0.0,0.0,0.0,5.0);
    text("0",0,15);
    line(5.0/(float)mercator.kilometerinpixels(),0.0,5.0/(float)mercator.kilometerinpixels(),5.0);
    text("5",5/(float)mercator.kilometerinpixels(),15);
    line(10.0/(float)mercator.kilometerinpixels(),0.0,10.0/(float)mercator.kilometerinpixels(),5.0);
    text("10",10/(float)mercator.kilometerinpixels(),15);
    line(0.0,5.0,10.0/(float)mercator.kilometerinpixels(),5.0);  
    text("kilometers",5.0/(float)mercator.kilometerinpixels(),25);
  }
  popMatrix();

  textAlign(LEFT);
  textFont(headerFont,16);
  if (showTimeBands) {
    text("Time to Travel from " + ellipseCentre.name,width/20,width/20);
  }
  else {
    text("Geographic Tube Map (click a station for travel time map)",width/20,width/20);
  }

  if (keyPressed && (key == 'g' || key == 'G')) {
    statIter = stations.values().iterator();
    while(statIter.hasNext()) {
      Station s = (Station)statIter.next();
      s.targetx = s.mapx;
      s.targety = s.mapy;
    }
    showTimeBands = false;
  }

}

void mouseReleased() {
  if (selected != null) {
    layoutAbout(selected);
    ellipseCentre = selected;
    ellipses = null;
  }
}

color stringToColor(String string) {
  if (!string.equals("NULL")) {
    return unhex("FF"+string.substring(1,7));
  }
  else {
    return 0;
  }
}


void layoutAbout(Station centre) {
  updateShortestPaths(centre);
  showTimeBands = true;
//  centre.targetx = width/2.0;
//  centre.targety = height/2.0;
  centre.targetx = centre.mapx;
  centre.targety = centre.mapy;
  Iterator statIter = stations.values().iterator();
  while(statIter.hasNext()) {
    Station s = (Station)statIter.next();
    layout(s,centre);
  }
}


void layout(Station a, Station centre) {
  float ang = atan2(a.mapy - centre.mapy, a.mapx - centre.mapx);
  float rad = pixelsPerMinute * (float)a.timeToCentre; // todo: limit to min(width/2,height/2)?
  a.targetx = centre.targetx + (rad * cos(ang));
  a.targety = centre.targety + (rad * sin(ang));    
}

// cribbed from here in double quick time: http://www.cs.cmu.edu/~crpalmer/sp/
void updateShortestPaths(Station centre) {

  Iterator statIter = stations.values().iterator();
  while (statIter.hasNext()) {
    Station s = (Station)statIter.next();
    s.timeToCentre = (s == centre) ? 0 : Integer.MAX_VALUE;
    s.pathParent = null;
  }
  Stack queue = new Stack();
  queue.push(centre); 

  while (queue.size() > 0) {
    Collections.sort(queue);
    Station v = (Station)queue.pop();
    for (int i = 0; i < v.conns.size(); i++) {
      Connection c = (Connection)v.conns.get(i);
      Station u = (c.a == v) ? c.b : c.a;
      if (c.time + v.timeToCentre < u.timeToCentre) {
        u.pathParent = v;
        u.timeToCentre = c.time + v.timeToCentre;
        queue.push(u);
      }
    }
  }

}
